diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 701c21585ac3d..89da08dee56a7 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "microsoft.dotnet.xharness.cli": { - "version": "1.0.0-prerelease.21065.1", + "version": "1.0.0-prerelease.21068.3", "commands": [ "xharness" ] diff --git a/.editorconfig b/.editorconfig index 4986cd7d19645..05c161c83ec6e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -196,5 +196,5 @@ indent_size = 2 # Shell scripts [*.sh] end_of_line = lf -[*.{cmd, bat}] +[*.{cmd,bat}] end_of_line = crlf diff --git a/Directory.Build.props b/Directory.Build.props index b0a8c187f224d..1f3e8c1d192cf 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -193,6 +193,10 @@ true false + + false diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 111dcf586ab5f..88501d67ddd53 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -15,9 +15,9 @@ Copyright (c) .NET Foundation. All rights reserved. Licensed under the Apache License, Version 2.0. Available at -https://github.com/aspnet/AspNetCore/blob/master/LICENSE.txt +https://github.com/dotnet/aspnetcore/blob/master/LICENSE.txt -License notice for Slicing-by-8 +License notice for Slicing-by-8 ------------------------------- http://sourceforge.net/projects/slicing-by-8/ @@ -66,7 +66,7 @@ shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. -License notice for Zlib +License notice for Zlib ----------------------- https://github.com/madler/zlib @@ -117,12 +117,12 @@ 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 +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 International Organization for Standardization @@ -232,7 +232,7 @@ noted) — feel free to use them however you please. The aggregate collection an descriptions are © 1997-2005 Sean Eron Anderson. The code and descriptions are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and without even the implied warranty of merchantability or fitness for a particular -purpose. +purpose. License notice for Brotli -------------------------------------- @@ -378,7 +378,7 @@ License notice for RFC 3492 --------------------------- The punycode implementation is based on the sample code in RFC 3492 - + Copyright (C) The Internet Society (2003). All Rights Reserved. This document and translations of it may be copied and furnished to @@ -448,7 +448,7 @@ ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. -License notice for Algorithm from RFC 4122 - +License notice for Algorithm from RFC 4122 - A Universally Unique IDentifier (UUID) URN Namespace ---------------------------------------------------- @@ -517,8 +517,8 @@ License notice for Greg Parker ------------------------------ Greg Parker gparker@cs.stanford.edu December 2000 -This code is in the public domain and may be copied or modified without -permission. +This code is in the public domain and may be copied or modified without +permission. License notice for libunwind based code ---------------------------------------- @@ -548,23 +548,23 @@ License notice for Printing Floating-Point Numbers (Dragon4) /****************************************************************************** Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com/ - + This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - + Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. - + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - + 3. This notice may not be removed or altered from any source distribution. ******************************************************************************/ diff --git a/docs/area-owners.md b/docs/area-owners.md index 4f2f62cc66594..e1ed6ea275aec 100644 --- a/docs/area-owners.md +++ b/docs/area-owners.md @@ -75,7 +75,7 @@ Note: Editing this file doesn't update the mapping used by the `@msftbot` issue | area-System.Diagnostics | @tommcdon | @tommcdon @krwq | | | area-System.Diagnostics.Process | @jeffhandley | @adamsitnik @eiriktsarpalis | | | area-System.Diagnostics.Tracing | @tommcdon | @noahfalk @tommcdon @tarekgh @Anipik | Packages:
| -| area-System.DirectoryServices | @tquerec | @tquerec @josephisenhour @joperezr | | +| area-System.DirectoryServices | @ericstj | @tquerec @josephisenhour @joperezr | | | area-System.Drawing | @jeffhandley | @safern @tannergooding | | | area-System.Dynamic.Runtime | @jaredpar | @cston @333fred | Archived component - limited churn/contributions (see [#27790](https://github.com/dotnet/runtime/issues/27790)) | | area-System.Formats.Asn1 | @jeffhandley | @bartonjs @eiriktsarpalis @GrabYourPitchforks | | diff --git a/docs/coding-guidelines/api-guidelines/nullability.md b/docs/coding-guidelines/api-guidelines/nullability.md index 807a0c3f91f68..8982b9ad69ddf 100644 --- a/docs/coding-guidelines/api-guidelines/nullability.md +++ b/docs/coding-guidelines/api-guidelines/nullability.md @@ -67,7 +67,7 @@ However, for existing virtual APIs that do not have any such strong guarantee do 4. How common is it in the case of (3) for such invocations to then dereference the result rather than passing it off to something else that accepts a `T?`? `Object.ToString` is arguably the most extreme case. Answering the above questions: -1. It is fairly easy in any reasonably-sized code base to find cases, intentional or otherwise, where `ToString` returns `null` in some cases (we've found examples in dotnet/corefx, dotnet/roslyn, NuGet/NuGet.Client, aspnet/AspNetCore, and so on). One of the most prevalent conditions for this are types that just return the value in a string field which may contain its default value of `null`, and in particular for structs where a ctor may not have even had a chance to run and validate an input. Guidance in the docs suggests that `ToString` shouldn't return `null` or `string.Empty`, but even the docs don't follow its own guidance. +1. It is fairly easy in any reasonably-sized code base to find cases, intentional or otherwise, where `ToString` returns `null` in some cases (we've found examples in dotnet/corefx, dotnet/roslyn, NuGet/NuGet.Client, dotnet/aspnetcore, and so on). One of the most prevalent conditions for this are types that just return the value in a string field which may contain its default value of `null`, and in particular for structs where a ctor may not have even had a chance to run and validate an input. Guidance in the docs suggests that `ToString` shouldn't return `null` or `string.Empty`, but even the docs don't follow its own guidance. 2. Thousands upon thousands of types we don't control override this method today. 3. It's common for helper routines to invoke via the base `object.ToString`, but many `ToString` uses are actually on derived types. This is particularly true when working in a code base that both defines a type and consumes its `ToString`. 4. Based on examination of several large code bases, we believe it to be relatively rare that the result of an `Object.ToString` call (made on the base) to be directly dereferenced. It's much more common to pass it to another method that accepts `string?`, such as `String.Concat`, `String.Format`, `Console.WriteLine`, logging utilities, and so on. And while we advocate that `ToString` results shouldn't be assumed to be in a particular machine-readable format and parsed, it's certainly the case that code bases do, such as using `Substring` on the result, but in such cases, the caller needs to understand the format of what's being rendered, which generally means they're working with a derived type rather than calling through the base `Object.ToString`. diff --git a/docs/project/profiling-api-status.md b/docs/project/profiling-api-status.md index 5e649414cea8e..a09792eb78fec 100644 --- a/docs/project/profiling-api-status.md +++ b/docs/project/profiling-api-status.md @@ -1,12 +1,13 @@ # Status of CoreCLR Profiler APIs -The CoreCLR project started with the codebase from the .NET Framework so all the profiler APIs present there are also present in the code here. This is the status of our testing and porting efforts for these APIs. +Below is a table of the version of CoreCLR that profiler support and testing was completed. Profiling may work prior to these versions, but there may be bugs and missing features. -## Platform test coverage - -- Windows on x86/x64/arm32 -- Linux on x86/x64/arm32 -- OSX +| | Windows | Linux | OSX | +| ----- | ------- | ----- | --- | +| x64 | 2.1 | 2.1 | 3.1 | +| x86 | 2.1 | N/A | N/A | +| arm32 | 3.1 | 3.1 | N/A | +| arm64 | 3.1 | 3.1 | TBA | ## Known issues @@ -14,14 +15,6 @@ The CoreCLR project started with the codebase from the .NET Framework so all the The implementation of this API was making some questionable assumptions about Windows OS API behavior in order to walk callstacks asynchronously. When operating in this async mode we aren't yet confident we can produce reasonable implementations for other platforms. Our understanding is that most users of this API are attempting to do sample based profiling. If so we think it may be easier to offer a runtime provided event stream of sample callstacks to accomplish the same scenario without needing the API, but we also haven't heard any demand for it. Feedback welcome! -### ReJIT on ARM - -ReJIT feature is only available on x86/x64 for now. - -### Profiler Attach/Detach - -We only support launch at the moment, see https://github.com/dotnet/runtime/issues/9886 - ### Any issues we missed? Please let us know and we will get it addressed. Thanks! diff --git a/docs/workflow/testing/libraries/testing-wasm.md b/docs/workflow/testing/libraries/testing-wasm.md index 22c26767da2c5..3979f9033a09b 100644 --- a/docs/workflow/testing/libraries/testing-wasm.md +++ b/docs/workflow/testing/libraries/testing-wasm.md @@ -28,6 +28,7 @@ PATH=/Users//.jsvu/:$PATH V8 ### Using Browser Instance It's possible to run tests in a browser instance: +#### Chrome - An installation of [ChromeDriver - WebDriver for Chrome](https://chromedriver.chromium.org) is required. Make sure to read [Downloads/Version Selection](https://chromedriver.chromium.org/downloads/version-selection) to setup a working installation of ChromeDriver. - Include the [ChromeDriver - WebDriver for Chrome](https://chromedriver.chromium.org) location in your PATH environment. Default is `/Users//.chromedriver` @@ -35,6 +36,15 @@ It's possible to run tests in a browser instance: PATH=/Users//.chromedriver:$PATH ``` +#### Gecko / Firefox + +- Requires gecko driver [Github repository of Mozilla](https://github.com/mozilla/geckodriver/releases) +- Include the [Github repository of Mozilla](https://github.com/mozilla/geckodriver/releases) location in your PATH environment. Default is `/Users//.geckodriver` + +```bash +PATH=/Users//.geckodriver:$PATH +``` + ## Building Libs and Tests for WebAssembly Now we're ready to build everything for WebAssembly (for more details, please read [this document](../../building/libraries/webassembly-instructions.md#building-everything)): @@ -101,6 +111,26 @@ To run all tests, including "outer loop" tests (which are typically slower and i MSBUILD_ARGS=/p:OuterLoop=true make -C src/mono/wasm/ run-browser-tests-System.AppContext ``` +### Running tests using different Browsers +It's possible to set a Browser explicitly by adding `--browser=` command line argument to `XHARNESS_COMMAND`: + +- CLI + ``` + XHARNESS_COMMAND="test-browser --browser=safari" ./dotnet.sh build /t:Test src/libraries/System.AppContext/tests /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=Release + ``` + +- Makefile target `run-browser-tests-` + + ``` + XHARNESS_BROWSER=firefox make -C src/mono/wasm/ run-browser-tests-System.AppContext + ``` + +At the moment supported values are: +- `chrome` +- `safari` +- `firefox` + +By default, `chrome` browser is used. ## Kicking off outer loop tests from GitHub Interface diff --git a/eng/Subsets.props b/eng/Subsets.props index 692812a9f7651..ce3049f51cb86 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -45,7 +45,7 @@ mono.llvm+ mono.llvm+ $(DefaultMonoSubsets)mono.wasmruntime+ - $(DefaultMonoSubsets)mono.aotcross+ + $(DefaultMonoSubsets)mono.aotcross+ $(DefaultMonoSubsets)mono.runtime+mono.corelib+mono.packages libs.native+libs.ref+libs.src+libs.pretest+libs.packages @@ -54,7 +54,8 @@ host.native - packs.product+packs.tests + packs.product + $(DefaultPacksSubsets)+packs.tests @@ -195,7 +196,8 @@ - + @@ -280,15 +282,15 @@ - - + + - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 87a40625c3096..9c17f123167a2 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -166,45 +166,45 @@ https://github.com/dotnet/runtime 38017c3935de95d0335bac04f4901ddfc2718656 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/dotnet/runtime - 4e13df4c4ee210850d134ec972569d64fedaa11a + 5c5bb6a340d04a476045d37436530297eff189b7 - + https://github.com/mono/linker - 9978bd03eab26081f5a353b84e516af5f33e88e7 + f3cb254461d6077f1b9c18c4a6a76c64c7869413 - + https://github.com/dotnet/xharness - ed5fbe0f7cbfc59b70ca078d9d9dd987044eb43e + ee50e1b0f769766598c0ad5071da888e66ec717a - + https://github.com/dotnet/xharness - ed5fbe0f7cbfc59b70ca078d9d9dd987044eb43e + ee50e1b0f769766598c0ad5071da888e66ec717a diff --git a/eng/Versions.props b/eng/Versions.props index dc26d92d872b3..ae4576ab5e41f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -6,7 +6,7 @@ 6 0 0 - alpha + preview 1 @@ -63,11 +63,11 @@ 5.9.0-preview.2 6.0.0-alpha.1.20612.4 - 6.0.0-alpha.1.21060.3 - 6.0.0-alpha.1.21060.3 + 6.0.0-alpha.1.21068.2 + 6.0.0-alpha.1.21068.2 3.1.0 - 6.0.0-alpha.1.21060.3 + 6.0.0-alpha.1.21068.2 1.2.0-beta.304 4.5.1 @@ -95,14 +95,14 @@ 4.7.0 4.7.0 4.7.0 - 6.0.0-alpha.1.21060.3 - 6.0.0-alpha.1.21060.3 + 6.0.0-alpha.1.21068.2 + 6.0.0-alpha.1.21068.2 4.3.0 4.5.4 4.5.0 1.1.1 4.3.0 - 6.0.0-alpha.1.21060.3 + 6.0.0-alpha.1.21068.2 5.0.0-beta.21062.1 5.0.0-beta.21062.1 @@ -141,8 +141,8 @@ 1.0.1-prerelease-00006 16.9.0-preview-20201201-01 - 1.0.0-prerelease.21065.1 - 1.0.0-prerelease.21065.1 + 1.0.0-prerelease.21068.3 + 1.0.0-prerelease.21068.3 2.4.1 2.4.2 1.3.0 @@ -153,7 +153,7 @@ 5.0.0-preview-20201009.2 - 6.0.0-alpha.1.21065.1 + 6.0.0-alpha.1.21072.3 6.0.0-alpha.1.21061.1 diff --git a/eng/build.ps1 b/eng/build.ps1 index 1edde94bd2872..fe3ff54689b4a 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -17,6 +17,7 @@ Param( [ValidateSet("Debug","Release")][string][Alias('lc')]$librariesConfiguration, [ValidateSet("CoreCLR","Mono")][string][Alias('rf')]$runtimeFlavor, [switch]$ninja, + [string]$cmakeargs, [Parameter(ValueFromRemainingArguments=$true)][String[]]$properties ) @@ -76,6 +77,7 @@ function Get-Help() { Write-Host "" Write-Host "Native build settings:" + Write-Host " -cmakeargs User-settable additional arguments passed to CMake." Write-Host " -ninja Use Ninja instead of MSBuild to run the native build." Write-Host "Command-line arguments not listed above are passed through to MSBuild." @@ -229,6 +231,7 @@ foreach ($argument in $PSBoundParameters.Keys) "allconfigurations" { $arguments += " /p:BuildAllConfigurations=true" } "properties" { $arguments += " " + $properties } "verbosity" { $arguments += " -$argument " + $($PSBoundParameters[$argument]) } + "cmakeargs" { $arguments += " /p:CMakeArgs=`"$($PSBoundParameters[$argument])`"" } "ninja" { $arguments += " /p:Ninja=$($PSBoundParameters[$argument])" } # configuration and arch can be specified multiple times, so they should be no-ops here "configuration" {} diff --git a/eng/illink.targets b/eng/illink.targets index b274575178469..ecbe07e843837 100644 --- a/eng/illink.targets +++ b/eng/illink.targets @@ -222,8 +222,6 @@ - - $(ILLinkArgs) -t $(ILLinkArgs) --strip-link-attributes false --ignore-link-attributes true @@ -245,7 +243,6 @@ Condition="'$(ILLinkTrimAssembly)' == 'true'" DependsOnTargets="SetCommonILLinkArgs"> - $(ILLinkArgs) -r $(TargetName) $(ILLinkArgs) -c skip @@ -309,6 +306,9 @@ <_DependencyDirectories Remove="@(_DependencyDirectories)" /> <_DependencyDirectories Include="%(_DependencyDirectoriesNoSlash.PathWithoutSlash)" /> + + visible + @@ -323,8 +323,8 @@ <_DotNetHostFileName>$([System.IO.Path]::GetFileName('$(DotNetTool)')) - &1 | grep -qi android; then + elif command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then __android_sdk_version=$(getprop ro.build.version.sdk) nonPortableBuildID="android.$__android_sdk_version-${buildArch}" elif [ "$targetOs" = "illumos" ]; then diff --git a/eng/native/init-os-and-arch.sh b/eng/native/init-os-and-arch.sh index 7bda16f77a275..a234b6b3119d7 100644 --- a/eng/native/init-os-and-arch.sh +++ b/eng/native/init-os-and-arch.sh @@ -3,7 +3,7 @@ # Use uname to determine what the OS is. OSName=$(uname -s) -if getprop ro.product.system.model 2>&1 | grep -qi android; then +if command -v getprop && getprop ro.product.system.model 2>&1 | grep -qi android; then OSName="Android" fi diff --git a/eng/pipelines/common/global-build-job.yml b/eng/pipelines/common/global-build-job.yml index 586e9df6d7bcd..d006c2cf29e14 100644 --- a/eng/pipelines/common/global-build-job.yml +++ b/eng/pipelines/common/global-build-job.yml @@ -11,7 +11,7 @@ parameters: variables: [] targetRid: '' timeoutInMinutes: '' - dependsOn: '' + dependsOn: [] pool: '' platform: '' condition: true @@ -89,11 +89,11 @@ jobs: - ${{ if eq(parameters.isOfficialBuild, true) }}: - template: /eng/pipelines/common/restore-internal-tools.yml - - ${{ if eq(parameters.monoCrossAOTTargetOS, 'Android') }}: + - ${{ each monoCrossAOTTargetOS in parameters.monoCrossAOTTargetOS }}: - task: DownloadPipelineArtifact@2 - displayName: Download AOT offset files + displayName: Download ${{monoCrossAOTTargetOS}} AOT offset files inputs: - artifact: Mono_Offsets_Android + artifact: Mono_Offsets_${{monoCrossAOTTargetOS}} path: '$(Build.SourcesDirectory)/artifacts/obj/mono/offsetfiles' - ${{ if in(parameters.osGroup, 'OSX', 'iOS', 'tvOS', 'Android') }}: diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index aad40983ef9e1..5ec4f832af03e 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -34,9 +34,9 @@ jobs: # Linux arm64 - ${{ if eq(parameters.platform, 'Linux_arm64') }}: - ${{ if eq(parameters.jobParameters.isFullMatrix, true) }}: - - (Ubuntu.1804.ArmArch.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-bfcd90a-20200127194925 + - (Ubuntu.1804.ArmArch.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-20210106155927-56c6673 - ${{ if eq(parameters.jobParameters.isFullMatrix, false) }}: - - (Ubuntu.1804.ArmArch.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-bfcd90a-20200127194925 + - (Ubuntu.1804.ArmArch.Open)Ubuntu.1804.ArmArch.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-20210106155927-56c6673 # Linux musl x64 - ${{ if eq(parameters.platform, 'Linux_musl_x64') }}: diff --git a/eng/pipelines/mono/templates/build-job.yml b/eng/pipelines/mono/templates/build-job.yml index 1b7c3d9abd8da..48b18b1c8b50c 100644 --- a/eng/pipelines/mono/templates/build-job.yml +++ b/eng/pipelines/mono/templates/build-job.yml @@ -13,8 +13,8 @@ parameters: isOfficialBuild: false crossBuild: false crossrootfsDir: '' - dependsOn: '' - monoCrossAOTTargetOS: '' + dependsOn: [] + monoCrossAOTTargetOS: [] dependOnEvaluatePaths: false ### Product build @@ -93,9 +93,9 @@ jobs: - ${{ if eq(parameters.runtimeVariant, 'llvmaot') }}: - name: llvmParameter value: /p:MonoEnableLLVM=true /p:MonoBundleLLVMOptimizer=true $(llvmCxxAbi) - - ${{ if eq(parameters.monoCrossAOTTargetOS, 'Android') }}: + - ${{ if gt(length(parameters.monoCrossAOTTargetOS),0) }}: - name: aotCrossParameter - value: /p:BuildMonoCrossAOT=true /p:SkipMonoCrossJitConfigure=true /p:BuildMonoAOTCrossCompilerOnly=true + value: /p:MonoCrossAOTTargetOS=${{join('+',parameters.monoCrossAOTTargetOS)}} /p:SkipMonoCrossJitConfigure=true /p:BuildMonoAOTCrossCompilerOnly=true - ${{ parameters.variables }} steps: @@ -112,11 +112,11 @@ jobs: - script: $(Build.SourcesDirectory)\eng\common\init-tools-native.cmd -InstallDirectory $(Build.SourcesDirectory)\native-tools -Force displayName: Install native dependencies - - ${{ if eq(parameters.monoCrossAOTTargetOS, 'Android') }}: + - ${{ each monoCrossAOTTargetOS in parameters.monoCrossAOTTargetOS }}: - task: DownloadPipelineArtifact@2 - displayName: Download AOT offset files + displayName: Download ${{monoCrossAOTTargetOS}} AOT offset files inputs: - artifact: Mono_Offsets_Android + artifact: Mono_Offsets_${{monoCrossAOTTargetOS}} path: '$(Build.SourcesDirectory)/artifacts/obj/mono/offsetfiles' - ${{ if in(parameters.osGroup, 'OSX', 'iOS','tvOS') }}: diff --git a/eng/pipelines/mono/templates/generate-offsets.yml b/eng/pipelines/mono/templates/generate-offsets.yml index 7b3117706ef5a..ff3b3e9d70ca7 100644 --- a/eng/pipelines/mono/templates/generate-offsets.yml +++ b/eng/pipelines/mono/templates/generate-offsets.yml @@ -62,10 +62,10 @@ jobs: # Build - ${{ if ne(parameters.osGroup, 'windows') }}: - - script: ./build$(scriptExt) -subset mono.aotcross -c $(buildConfig) -arch $(archType) $(osOverride) -ci $(officialBuildIdArg) /p:MonoGenerateOffsetsOnly=true + - script: ./build$(scriptExt) -subset mono.aotcross -c $(buildConfig) -arch $(archType) $(osOverride) -ci $(officialBuildIdArg) /p:MonoGenerateOffsetsOSGroups=$(osGroup) displayName: Generate AOT offsets - ${{ if eq(parameters.osGroup, 'windows') }}: - - script: build$(scriptExt) -subset mono.aotcross -c $(buildConfig) -arch $(archType) $(osOverride) -ci $(officialBuildIdArg) /p:MonoGenerateOffsetsOnly=true + - script: build$(scriptExt) -subset mono.aotcross -c $(buildConfig) -arch $(archType) $(osOverride) -ci $(officialBuildIdArg) /p:MonoGenerateOffsetsOSGroups=$(osGroup) displayName: Generate AOT offsets # Upload offset files diff --git a/eng/pipelines/runtime-official.yml b/eng/pipelines/runtime-official.yml index 2bab2f0bfcf87..6a3dfd842094f 100644 --- a/eng/pipelines/runtime-official.yml +++ b/eng/pipelines/runtime-official.yml @@ -132,9 +132,12 @@ stages: buildConfig: release platforms: - Android_x64 + - Browser_wasm + - tvOS_x64 + - iOS_x64 # - # Build Mono release Android AOT cross-compiler + # Build Mono release AOT cross-compilers # - template: /eng/pipelines/common/platform-matrix.yml parameters: @@ -142,19 +145,50 @@ stages: runtimeFlavor: mono buildConfig: release platforms: - - OSX_x64 - Linux_x64 jobParameters: - buildArgs: -s mono+libs+host+packs -c $(_BuildConfig) - /p:BuildMonoCrossAOT=true /p:SkipMonoCrossJitConfigure=true /p:BuildMonoAOTCrossCompilerOnly=true - nameSuffix: AndroidAOT_Mono - runtimeVariant: crossandroid - dependsOn: mono_android_offsets - monoCrossAOTTargetOS: Android + buildArgs: -s mono+packs -c $(_BuildConfig) + /p:MonoCrossAOTTargetOS=Android+Browser /p:SkipMonoCrossJitConfigure=true /p:BuildMonoAOTCrossCompilerOnly=true + nameSuffix: CrossAOT_Mono + runtimeVariant: crossaot + dependsOn: + - mono_android_offsets + - mono_browser_offsets + monoCrossAOTTargetOS: + - Android + - Browser isOfficialBuild: ${{ variables.isOfficialBuild }} extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml extraStepsParameters: name: MonoRuntimePacks + + - template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + runtimeFlavor: mono + buildConfig: release + platforms: + - OSX_x64 + jobParameters: + buildArgs: -s mono+packs -c $(_BuildConfig) + /p:MonoCrossAOTTargetOS=Android+Browser+tvOS+iOS /p:SkipMonoCrossJitConfigure=true /p:BuildMonoAOTCrossCompilerOnly=true + nameSuffix: CrossAOT_Mono + runtimeVariant: crossaot + dependsOn: + - mono_android_offsets + - mono_browser_offsets + - mono_tvos_offsets + - mono_ios_offsets + monoCrossAOTTargetOS: + - Android + - Browser + - tvOS + - iOS + isOfficialBuild: ${{ variables.isOfficialBuild }} + extraStepsTemplate: /eng/pipelines/common/upload-intermediate-artifacts-step.yml + extraStepsParameters: + name: MonoRuntimePacks + # # Build Mono LLVM runtime packs # diff --git a/eng/pipelines/runtime-staging.yml b/eng/pipelines/runtime-staging.yml index a47272362f346..f99e5a399f7b4 100644 --- a/eng/pipelines/runtime-staging.yml +++ b/eng/pipelines/runtime-staging.yml @@ -77,6 +77,45 @@ jobs: testGroup: innerloop nameSuffix: AllSubsets_Mono buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true + timeoutInMinutes: 180 + 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['isFullMatrix'], true)) + # extra steps, run tests + extraStepsTemplate: /eng/pipelines/libraries/helix.yml + extraStepsParameters: + creator: dotnet-bot + testRunNamePrefixSuffix: Mono_$(_BuildConfig) + condition: >- + or( + eq(variables['librariesContainsChange'], true), + eq(variables['monoContainsChange'], true), + eq(variables['isFullMatrix'], true)) + +# +# Build the whole product using Mono and run libraries tests +# +- template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/common/global-build-job.yml + helixQueuesTemplate: /eng/pipelines/libraries/helix-queues-setup.yml + buildConfig: Release + runtimeFlavor: mono + platforms: + - Browser_wasm + variables: + # map dependencies variables to local variables + - name: librariesContainsChange + value: $[ dependencies.evaluate_paths.outputs['SetPathVars_libraries.containsChange'] ] + - name: monoContainsChange + value: $[ dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'] ] + jobParameters: + testGroup: innerloop + nameSuffix: AllSubsets_Mono_AOT + buildArgs: -s mono+libs+host+packs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:EnableAggressiveTrimming=true /p:RunAOTCompilation=true timeoutInMinutes: 120 condition: >- or( @@ -89,6 +128,8 @@ jobs: extraStepsParameters: creator: dotnet-bot testRunNamePrefixSuffix: Mono_$(_BuildConfig) + scenarios: + - normal condition: >- or( eq(variables['librariesContainsChange'], true), diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index da9d540235a96..14b5cf0bdcbee 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -192,6 +192,9 @@ jobs: buildConfig: release platforms: - Android_x64 + - Browser_wasm + - tvOS_x64 + - iOS_x64 jobParameters: condition: >- or( @@ -287,7 +290,6 @@ jobs: eq(variables['monoContainsChange'], true), eq(variables['isFullMatrix'], true)) - # # Build the whole product using Mono and run libraries tests # @@ -476,7 +478,7 @@ jobs: eq(variables['isFullMatrix'], true)) # -# Build Mono release Android AOT cross-compiler +# Build Mono release AOT cross-compilers # Only when mono changed # - template: /eng/pipelines/common/platform-matrix.yml @@ -485,7 +487,6 @@ jobs: runtimeFlavor: mono buildConfig: release platforms: - - OSX_x64 - Linux_x64 # - Linux_arm64 # - Linux_musl_arm64 @@ -494,9 +495,38 @@ jobs: # - windows_arm # - windows_arm64 jobParameters: - runtimeVariant: crossandroid - dependsOn: mono_android_offsets - monoCrossAOTTargetOS: Android + runtimeVariant: crossaot + dependsOn: + - mono_android_offsets + - mono_browser_offsets + monoCrossAOTTargetOS: + - Android + - Browser + condition: >- + or( + eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), + eq(dependencies.evaluate_paths.outputs['SetPathVars_installer.containsChange'], true), + eq(variables['isFullMatrix'], true)) + +- template: /eng/pipelines/common/platform-matrix.yml + parameters: + jobTemplate: /eng/pipelines/mono/templates/build-job.yml + runtimeFlavor: mono + buildConfig: release + platforms: + - OSX_x64 + jobParameters: + runtimeVariant: crossaot + dependsOn: + - mono_android_offsets + - mono_browser_offsets + - mono_tvos_offsets + - mono_ios_offsets + monoCrossAOTTargetOS: + - Android + - Browser + - tvOS + - iOS condition: >- or( eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true), diff --git a/eng/resolveContract.targets b/eng/resolveContract.targets index 06084755937f4..f41be68744199 100644 --- a/eng/resolveContract.targets +++ b/eng/resolveContract.targets @@ -1,32 +1,22 @@ - $(MicrosoftNetCoreAppRefPackRefDir) - - $(ContractDependencyPaths);@(ReferencePath->'%(RelativeDir)'->Distinct()) + + @(ReferencePath->'%(RelativeDir)'->Distinct()) $(LibrariesProjectRoot)$(MSBuildProjectName)\ref\$(MSBuildProjectName).csproj true - $(NetCoreAppCurrentRefPath)$(TargetFileName) - <_TargetFrameworkWithoutPlatform>$([System.Text.RegularExpressions.Regex]::Replace('$(TargetFramework)', '(-[^;]+)', '')) - $([MSBuild]::NormalizePath('$(BaseOutputPath)', 'ref', '$(_TargetFrameworkWithoutPlatform)-$(Configuration)', '$(TargetFileName)')) false - - - - - false - ResolvedMatchingContract - + + + + - diff --git a/eng/testing/RunnerTemplate.sh b/eng/testing/RunnerTemplate.sh index 25b5124c84308..a8bdcb1d47884 100644 --- a/eng/testing/RunnerTemplate.sh +++ b/eng/testing/RunnerTemplate.sh @@ -177,11 +177,17 @@ fi if [[ "$(uname -s)" == "Linux" && $test_exitcode -ne 0 ]]; then if [ -n "$HELIX_WORKITEM_PAYLOAD" ]; then - have_sleep=$(which sleep) - if [ -x "$have_sleep" ]; then - echo Waiting a few seconds for any dump to be written.. - sleep 10s - fi + + # For abrupt failures, in Helix, dump some of the kernel log, in case there is a hint + if [[ $test_exitcode -ne 1 ]]; then + dmesg | tail -50 + fi + + have_sleep=$(which sleep) + if [ -x "$have_sleep" ]; then + echo Waiting a few seconds for any dump to be written.. + sleep 10s + fi fi echo cat /proc/sys/kernel/core_pattern: $(cat /proc/sys/kernel/core_pattern) diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index ae231929cafce..cf570790f1a48 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -105,6 +105,8 @@ + + - + Include="%(ResolvedFileToPublish.FullPath)" /> diff --git a/global.json b/global.json index 51190f33517bc..9262b90660436 100644 --- a/global.json +++ b/global.json @@ -17,7 +17,7 @@ "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21062.10", "Microsoft.DotNet.SharedFramework.Sdk": "6.0.0-beta.21062.10", "Microsoft.FIX-85B6-MERGE-9C38-CONFLICT": "1.0.0", - "Microsoft.NET.Sdk.IL": "6.0.0-alpha.1.21060.3", + "Microsoft.NET.Sdk.IL": "6.0.0-alpha.1.21068.2", "Microsoft.Build.NoTargets": "2.0.1", "Microsoft.Build.Traversal": "2.1.1" } diff --git a/src/coreclr/CMakeLists.txt b/src/coreclr/CMakeLists.txt index ddb50946aeb0a..9c9ba76da063e 100644 --- a/src/coreclr/CMakeLists.txt +++ b/src/coreclr/CMakeLists.txt @@ -96,6 +96,13 @@ if(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_BUILD_SUBSET_RUNTIME) add_subdirectory(gc/sample) endif() +#------------------------------------- +# Include directory directives +#------------------------------------- +# Include the basic prebuilt headers - required for getting fileversion resource details. +include_directories("pal/prebuilt/inc") +include_directories("../../artifacts/obj/coreclr") + add_subdirectory(tools/aot/jitinterface) # Above projects do not build with these compile options @@ -112,13 +119,6 @@ endif(CLR_CMAKE_HOST_WIN32) #---------------------------------- include(clrdefinitions.cmake) -#------------------------------------- -# Include directory directives -#------------------------------------- -# Include the basic prebuilt headers - required for getting fileversion resource details. -include_directories("pal/prebuilt/inc") -include_directories("../../artifacts/obj/coreclr") - if(FEATURE_STANDALONE_GC) add_definitions(-DFEATURE_STANDALONE_GC) if (CLR_CMAKE_BUILD_SUBSET_RUNTIME) diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj index 36b60ed6663a3..04505e906c670 100644 --- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -57,19 +57,18 @@ x64 false - TARGET_64BIT;TARGET_AMD64;$(DefineConstants) + $(DefineConstants);TARGET_AMD64 x86 - TARGET_32BIT;$(DefineConstants) arm - TARGET_32BIT;TARGET_ARM;$(DefineConstants) + $(DefineConstants);TARGET_ARM AnyCPU - TARGET_64BIT;TARGET_ARM64;$(DefineConstants) + $(DefineConstants);TARGET_ARM64 diff --git a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs index 07988b1e8b710..fef2f90be0f68 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs @@ -57,7 +57,7 @@ protected Delegate(object target, string method) // This constructor is called from a class to generate a // delegate based upon a static method name and the Type object // for the class defining the method. - protected Delegate([DynamicallyAccessedMembers(AllMethods)] Type target, string method) + protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method) { if (target == null) throw new ArgumentNullException(nameof(target)); @@ -258,7 +258,7 @@ protected virtual MethodInfo GetMethodImpl() } // V1 API. - public static Delegate? CreateDelegate(Type type, [DynamicallyAccessedMembers(AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) + public static Delegate? CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -417,8 +417,11 @@ internal static Delegate CreateDelegateNoSecurityCheck(Type type, object? target // // internal implementation details (FCALLS and utilities) // + + // BindToMethodName is annotated as DynamicallyAccessedMemberTypes.All because it will bind to non-public methods + // on a base type of methodType. Using All is currently the only way ILLinker will preserve these methods. [MethodImpl(MethodImplOptions.InternalCall)] - private extern bool BindToMethodName(object? target, [DynamicallyAccessedMembers(AllMethods)] RuntimeType methodType, string method, DelegateBindingFlags flags); + private extern bool BindToMethodName(object? target, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] RuntimeType methodType, string method, DelegateBindingFlags flags); [MethodImpl(MethodImplOptions.InternalCall)] private extern bool BindToMethodInfo(object? target, IRuntimeMethodInfo method, RuntimeType methodType, DelegateBindingFlags flags); diff --git a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.cs b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.cs index eb061b88205fe..f650ffae48ab2 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/MulticastDelegate.cs @@ -33,7 +33,7 @@ protected MulticastDelegate(object target, string method) : base(target, method) // This constructor is called from a class to generate a // delegate based upon a static method name and the Type object // for the class defining the method. - protected MulticastDelegate([DynamicallyAccessedMembers(AllMethods)] Type target, string method) : base(target, method) + protected MulticastDelegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method) : base(target, method) { } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/ISymWrapperCore.cs b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/ISymWrapperCore.cs index 0d38f64c75945..39f4eac6016ea 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/ISymWrapperCore.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/ISymWrapperCore.cs @@ -502,7 +502,7 @@ private struct ISymUnmanagedWriter //-------------------------------------------------------------------------------------- internal sealed class PunkSafeHandle : SafeHandle { - internal PunkSafeHandle() + public PunkSafeHandle() : base((IntPtr)0, true) { } diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs index 1e377a9978f48..660665c3046e5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs @@ -54,6 +54,20 @@ public enum CreateObjectFlags /// Ignore any internal caching and always create a unique instance. /// UniqueInstance = 2, + + /// + /// Defined when COM aggregation is involved (that is an inner instance supplied). + /// + Aggregation = 4, + + /// + /// Check if the supplied instance is actually a wrapper and if so return the underlying + /// managed object rather than creating a new wrapper. + /// + /// + /// This matches the built-in RCW semantics for COM interop. + /// + Unwrap = 8, } /// @@ -228,7 +242,7 @@ private static bool TryGetOrCreateComInterfaceForObjectInternal(ComWrappers impl public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags) { object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, flags, null, out obj)) + if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, IntPtr.Zero, flags, null, out obj)) throw new ArgumentNullException(nameof(externalComObject)); return obj!; @@ -279,12 +293,33 @@ public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateOb /// If the instance already has an associated external object a will be thrown. /// public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper) + { + return GetOrRegisterObjectForComInstance(externalComObject, flags, wrapper, IntPtr.Zero); + } + + /// + /// Get the currently registered managed object or uses the supplied managed object and registers it. + /// + /// Object to import for usage into the .NET runtime. + /// Flags used to describe the external object. + /// The to be used as the wrapper for the external object + /// Inner for COM aggregation scenarios + /// Returns a managed object associated with the supplied external COM object. + /// + /// This method override is for registering an aggregated COM instance with its associated inner. The inner + /// will be released when the associated wrapper is eventually freed. Note that it will be released on a thread + /// in an unknown apartment state. If the supplied inner is not known to be a free-threaded instance then + /// it is advised to not supply the inner. + /// + /// If the instance already has an associated external object a will be thrown. + /// + public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper, IntPtr inner) { if (wrapper == null) - throw new ArgumentNullException(nameof(externalComObject)); + throw new ArgumentNullException(nameof(wrapper)); object? obj; - if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, flags, wrapper, out obj)) + if (!TryGetOrCreateObjectForComInstanceInternal(this, externalComObject, inner, flags, wrapper, out obj)) throw new ArgumentNullException(nameof(externalComObject)); return obj!; @@ -295,6 +330,7 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create /// /// The implementation to use when creating the managed object. /// Object to import for usage into the .NET runtime. + /// The inner instance if aggregation is involved /// Flags used to describe the external object. /// The to be used as the wrapper for the external object. /// The managed object associated with the supplied external COM object or null if it could not be created. @@ -302,18 +338,28 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create /// /// If is null, the global instance (if registered) will be used. /// - private static bool TryGetOrCreateObjectForComInstanceInternal(ComWrappers impl, IntPtr externalComObject, CreateObjectFlags flags, object? wrapperMaybe, out object? retValue) + private static bool TryGetOrCreateObjectForComInstanceInternal( + ComWrappers impl, + IntPtr externalComObject, + IntPtr innerMaybe, + CreateObjectFlags flags, + object? wrapperMaybe, + out object? retValue) { if (externalComObject == IntPtr.Zero) throw new ArgumentNullException(nameof(externalComObject)); + // If the inner is supplied the Aggregation flag should be set. + if (innerMaybe != IntPtr.Zero && !flags.HasFlag(CreateObjectFlags.Aggregation)) + throw new InvalidOperationException(SR.InvalidOperation_SuppliedInnerMustBeMarkedAggregation); + object? wrapperMaybeLocal = wrapperMaybe; retValue = null; - return TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack.Create(ref impl), impl.id, externalComObject, flags, ObjectHandleOnStack.Create(ref wrapperMaybeLocal), ObjectHandleOnStack.Create(ref retValue)); + return TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack.Create(ref impl), impl.id, externalComObject, innerMaybe, flags, ObjectHandleOnStack.Create(ref wrapperMaybeLocal), ObjectHandleOnStack.Create(ref retValue)); } [DllImport(RuntimeHelpers.QCall)] - private static extern bool TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack comWrappersImpl, long wrapperId, IntPtr externalComObject, CreateObjectFlags flags, ObjectHandleOnStack wrapper, ObjectHandleOnStack retValue); + private static extern bool TryGetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack comWrappersImpl, long wrapperId, IntPtr externalComObject, IntPtr innerMaybe, CreateObjectFlags flags, ObjectHandleOnStack wrapper, ObjectHandleOnStack retValue); /// /// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime. diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Interlocked.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Interlocked.CoreCLR.cs index 4d7d844085929..15a9cc87b7e60 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Interlocked.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Interlocked.CoreCLR.cs @@ -85,14 +85,6 @@ public static long Decrement(ref long location) => [return: NotNullIfNotNull("location1")] public static extern object? Exchange([NotNullIfNotNull("value")] ref object? location1, object? value); - /// Sets a platform-specific handle or pointer to a specified value and returns the original value, as an atomic operation. - /// The variable to set to the specified value. - /// The value to which the parameter is set. - /// The original value of . - /// The address of location1 is a null pointer. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern IntPtr Exchange(ref IntPtr location1, IntPtr value); - // The below whole method reduces to a single call to Exchange(ref object, object) but // the JIT thinks that it will generate more native code than it actually does. @@ -156,15 +148,6 @@ public static T Exchange([NotNullIfNotNull("value")] ref T location1, T value [return: NotNullIfNotNull("location1")] public static extern object? CompareExchange(ref object? location1, object? value, object? comparand); - /// Compares two platform-specific handles or pointers for equality and, if they are equal, replaces the first one. - /// The destination , whose value is compared with the value of and possibly replaced by . - /// The that replaces the destination value if the comparison results in equality. - /// The that is compared to the value at . - /// The original value in . - /// The address of is a null pointer. - [MethodImpl(MethodImplOptions.InternalCall)] - public static extern IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand); - // Note that getILIntrinsicImplementationForInterlocked() in vm\jitinterface.cpp replaces // the body of the following method with the the following IL: // ldarg.0 diff --git a/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs b/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs index 7a9dbf91e201c..9a00284c9a3c7 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs @@ -14,7 +14,7 @@ namespace System { - internal class SafeTypeNameParserHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeTypeNameParserHandle : SafeHandleZeroOrMinusOneIsInvalid { #region QCalls [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)] diff --git a/src/coreclr/ToolBox/superpmi/superpmi-shared/agnostic.h b/src/coreclr/ToolBox/superpmi/superpmi-shared/agnostic.h index b67c4fa7980ad..b5e4cc43c2d23 100644 --- a/src/coreclr/ToolBox/superpmi/superpmi-shared/agnostic.h +++ b/src/coreclr/ToolBox/superpmi/superpmi-shared/agnostic.h @@ -465,31 +465,27 @@ struct Agnostic_IsCompatibleDelegate struct Agnostic_PgoInstrumentationSchema { - DWORDLONG Offset; - ICorJitInfo::PgoInstrumentationKind InstrumentationKind; - int32_t ILOffset; - int32_t Count; - int32_t Other; + DWORDLONG Offset; // size_t + DWORD InstrumentationKind; // ICorJitInfo::PgoInstrumentationKind + DWORD ILOffset; // int32_t + DWORD Count; // int32_t + DWORD Other; // int32_t }; struct Agnostic_AllocPgoInstrumentationBySchema { - DWORDLONG address; - DWORD count; + DWORDLONG instrumentationDataAddress; DWORD schema_index; - DWORD schemaCount; + DWORD countSchemaItems; DWORD result; }; struct Agnostic_GetPgoInstrumentationResults { - DWORD count; - DWORD pBlockCounts_index; - DWORD numRuns; - DWORD schemaCount; - DWORD dataByteCount; + DWORD countSchemaItems; DWORD schema_index; DWORD data_index; + DWORD dataByteCount; DWORD result; }; diff --git a/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.cpp b/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.cpp index 9ed4a7b507482..269a2b0061852 100644 --- a/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.cpp +++ b/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.cpp @@ -1212,9 +1212,10 @@ void MethodContext::recResolveToken(CORINFO_RESOLVED_TOKEN* pResolvedToken, DWOR } void MethodContext::dmpResolveToken(const Agnostic_CORINFO_RESOLVED_TOKENin& key, const ResolveTokenValue& value) { - printf("ResolveToken key: %s\n", SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKENin(key).c_str()); - printf(", value: %s excp-%08X", SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKENout(value.tokenOut).c_str(), - value.exceptionCode); + printf("ResolveToken key: %s, value: %s excp-%08X", + SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKENin(key).c_str(), + SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKENout(value.tokenOut).c_str(), + value.exceptionCode); } void MethodContext::repResolveToken(CORINFO_RESOLVED_TOKEN* pResolvedToken, DWORD* exceptionCode) { @@ -1341,17 +1342,18 @@ void MethodContext::recGetCallInfo(CORINFO_RESOLVED_TOKEN* pResolvedToken, } void MethodContext::dmpGetCallInfo(const Agnostic_GetCallInfo& key, const Agnostic_CORINFO_CALL_INFO& value) { - printf("GetCallInfo key rt{%s} crt{%s} ch-%016llX flg-%08X\n", - SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ResolvedToken).c_str(), - SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ConstrainedResolvedToken).c_str(), key.callerHandle, - key.flags); - printf(", value mth-%016llX, mf-%08X cf-%08X" + printf("GetCallInfo key rt{%s} crt{%s} ch-%016llX flg-%08X" + ", value mth-%016llX, mf-%08X cf-%08X" " sig-%s" " vsig-%s" " ipl{at-%08X hnd-%016llX}" " sdi-%08X" " excp-%08X" " stubLookup{%s}", + SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ResolvedToken).c_str(), + SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ConstrainedResolvedToken).c_str(), + key.callerHandle, + key.flags, value.hMethod, value.methodFlags, value.classFlags, SpmiDumpHelper::DumpAgnostic_CORINFO_SIG_INFO(value.sig, GetCallInfo, SigInstHandleMap).c_str(), SpmiDumpHelper::DumpAgnostic_CORINFO_SIG_INFO(value.verSig, GetCallInfo, SigInstHandleMap).c_str(), value.instParamLookup.accessType, @@ -2786,10 +2788,12 @@ void MethodContext::recEmbedGenericHandle(CORINFO_RESOLVED_TOKEN* pResolve void MethodContext::dmpEmbedGenericHandle(const Agnostic_EmbedGenericHandle& key, const Agnostic_CORINFO_GENERICHANDLE_RESULT& value) { - printf("EmbedGenericHandle key rt{%s} emb-%u\n", - SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ResolvedToken).c_str(), key.fEmbedParent); - printf(", value %s", SpmiDumpHelper::DumpAgnostic_CORINFO_LOOKUP(value.lookup).c_str()); - printf(" cth-%016llX ht-%u", value.compileTimeHandle, value.handleType); + printf("EmbedGenericHandle key rt{%s} emb-%u, value %s cth-%016llX ht-%u", + SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ResolvedToken).c_str(), + key.fEmbedParent, + SpmiDumpHelper::DumpAgnostic_CORINFO_LOOKUP(value.lookup).c_str(), + value.compileTimeHandle, + value.handleType); } void MethodContext::repEmbedGenericHandle(CORINFO_RESOLVED_TOKEN* pResolvedToken, bool fEmbedParent, @@ -3079,7 +3083,7 @@ void MethodContext::recGetFieldInfo(CORINFO_RESOLVED_TOKEN* pResolvedToken, } void MethodContext::dmpGetFieldInfo(const Agnostic_GetFieldInfo& key, const Agnostic_CORINFO_FIELD_INFO& value) { - printf("GetFieldInfo key ch-%016llX flg-%08X rt{%s}\n", key.callerHandle, key.flags, + printf("GetFieldInfo key ch-%016llX flg-%08X rt{%s}", key.callerHandle, key.flags, SpmiDumpHelper::DumpAgnostic_CORINFO_RESOLVED_TOKEN(key.ResolvedToken).c_str()); printf(", value fa-%u fflg-%08X hlp-%u off-%u fT-%u(%s) sT-%016llX aa-%u hnum-%u na-%u {", value.fieldAccessor, @@ -5045,7 +5049,6 @@ void MethodContext::recGetFieldThreadLocalStoreID(CORINFO_FIELD_HANDLE field, vo { if (GetFieldThreadLocalStoreID == nullptr) GetFieldThreadLocalStoreID = new LightWeightMap(); - ; DLD value; @@ -5072,60 +5075,113 @@ DWORD MethodContext::repGetFieldThreadLocalStoreID(CORINFO_FIELD_HANDLE field, v } -void MethodContext::recAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, UINT32 countSchemaItems, BYTE** pInstrumentationData, HRESULT result) +void MethodContext::recAllocPgoInstrumentationBySchema( + CORINFO_METHOD_HANDLE ftnHnd, + ICorJitInfo::PgoInstrumentationSchema* pSchema, + UINT32 countSchemaItems, + BYTE** pInstrumentationData, + HRESULT result) { if (AllocPgoInstrumentationBySchema == nullptr) AllocPgoInstrumentationBySchema = new LightWeightMap(); Agnostic_AllocPgoInstrumentationBySchema value; - value.schemaCount = countSchemaItems; - value.address = CastPointer(*pInstrumentationData); + // NOTE: we store the `*pInstrumentationData` address, but not any data at that address. This makes sense, because when this is called, + // there is no data at the address. The JIT won't read/write the data, but only generate code to write to the buffer. + value.instrumentationDataAddress = CastPointer(*pInstrumentationData); + + // For the schema, note that we record *after* calling the VM API. Thus, it has already filled in the `Offset` fields. + // We save the "IN" parts to verify against the replayed call. + value.countSchemaItems = countSchemaItems; Agnostic_PgoInstrumentationSchema* agnosticSchema = (Agnostic_PgoInstrumentationSchema*)malloc(sizeof(Agnostic_PgoInstrumentationSchema) * countSchemaItems); for (UINT32 i = 0; i < countSchemaItems; i++) { - agnosticSchema[i].Offset = pSchema[i].Offset; - agnosticSchema[i].InstrumentationKind = pSchema[i].InstrumentationKind; - agnosticSchema[i].ILOffset = pSchema[i].ILOffset; - agnosticSchema[i].Count = pSchema[i].Count; - agnosticSchema[i].Other = pSchema[i].Other; + agnosticSchema[i].Offset = (DWORDLONG)pSchema[i].Offset; + agnosticSchema[i].InstrumentationKind = (DWORD)pSchema[i].InstrumentationKind; + agnosticSchema[i].ILOffset = (DWORD)pSchema[i].ILOffset; + agnosticSchema[i].Count = (DWORD)pSchema[i].Count; + agnosticSchema[i].Other = (DWORD)pSchema[i].Other; } value.schema_index = AllocPgoInstrumentationBySchema->AddBuffer((unsigned char*)agnosticSchema, sizeof(Agnostic_PgoInstrumentationSchema) * countSchemaItems); free(agnosticSchema); value.result = (DWORD)result; + // Even though `countSchemaItems` and (most of) the `pSchema` array are IN parameters, they do not contribute to the lookup key; + // the `ftnHnd` is the sole key, and the schema passed in for the function is expected to be the same every time the same function + // handle is used. AllocPgoInstrumentationBySchema->Add(CastHandle(ftnHnd), value); } void MethodContext::dmpAllocPgoInstrumentationBySchema(DWORDLONG key, const Agnostic_AllocPgoInstrumentationBySchema& value) { - printf("AllocPgoInstrumentationBySchema key ftn-%016llX, value addr-%016llX cnt-%u res-%08X", key, value.address, value.schemaCount, value.result); - Agnostic_PgoInstrumentationSchema* pBuf = - (Agnostic_PgoInstrumentationSchema*)AllocPgoInstrumentationBySchema->GetBuffer(value.schema_index); + printf("AllocPgoInstrumentationBySchema key ftn-%016llX, value res-%08X addr-%016llX cnt-%u schema{\n", + key, value.result, value.instrumentationDataAddress, value.countSchemaItems); - for (UINT32 i = 0; i < value.schemaCount; i++) + if (value.countSchemaItems > 0) { - printf(" Offset %016llX ILOffset %u Kind %u Count %u Other %u\n", pBuf[i].Offset, pBuf[i].ILOffset, pBuf[i].InstrumentationKind, pBuf[i].Count, pBuf[i].Other); + Agnostic_PgoInstrumentationSchema* pBuf = + (Agnostic_PgoInstrumentationSchema*)AllocPgoInstrumentationBySchema->GetBuffer(value.schema_index); + + printf("\n"); + for (DWORD i = 0; i < value.countSchemaItems; i++) + { + printf(" %u-{Offset %016llX ILOffset %u Kind %u(0x%x) Count %u Other %u}\n", + i, pBuf[i].Offset, pBuf[i].ILOffset, pBuf[i].InstrumentationKind, pBuf[i].InstrumentationKind, pBuf[i].Count, pBuf[i].Other); + } } + printf("}"); } -DWORD MethodContext::repAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, UINT32 countSchemaItems, BYTE** pInstrumentationData) +HRESULT MethodContext::repAllocPgoInstrumentationBySchema( + CORINFO_METHOD_HANDLE ftnHnd, + ICorJitInfo::PgoInstrumentationSchema* pSchema, + UINT32 countSchemaItems, + BYTE** pInstrumentationData) { + AssertCodeMsg(AllocPgoInstrumentationBySchema != nullptr, EXCEPTIONCODE_MC, "Found null AllocPgoInstrumentationBySchema for %016llX", CastHandle(ftnHnd)); + AssertCodeMsg(AllocPgoInstrumentationBySchema->GetIndex(CastHandle(ftnHnd)) != -1, EXCEPTIONCODE_MC, "AllocPgoInstrumentationBySchema: Didn't find %016llX", CastHandle(ftnHnd)); + Agnostic_AllocPgoInstrumentationBySchema value; value = AllocPgoInstrumentationBySchema->Get(CastHandle(ftnHnd)); - if (countSchemaItems != value.schemaCount) + if (value.countSchemaItems != countSchemaItems) { - LogWarning("AllocPgoInstrumentationBySchema mismatch: record %d, replay %d", value.schemaCount, countSchemaItems); + LogError("AllocPgoInstrumentationBySchema mismatch: countSchemaItems record %d, replay %d", value.countSchemaItems, countSchemaItems); } HRESULT result = (HRESULT)value.result; Agnostic_PgoInstrumentationSchema* pAgnosticSchema = (Agnostic_PgoInstrumentationSchema*)AllocPgoInstrumentationBySchema->GetBuffer(value.schema_index); size_t maxOffset = 0; - for (UINT32 iSchema = 0; iSchema < countSchemaItems && iSchema < value.schemaCount; iSchema++) + for (UINT32 iSchema = 0; iSchema < countSchemaItems && iSchema < value.countSchemaItems; iSchema++) { + // Everything but `Offset` field is an IN argument, so verify it against what we stored (since we didn't use these + // IN arguments as part of the key). + + if ((ICorJitInfo::PgoInstrumentationKind)pAgnosticSchema[iSchema].InstrumentationKind != pSchema[iSchema].InstrumentationKind) + { + LogError("AllocPgoInstrumentationBySchema mismatch: [%d].InstrumentationKind record %d, replay %d", + iSchema, pAgnosticSchema[iSchema].InstrumentationKind, (DWORD)pSchema[iSchema].InstrumentationKind); + } + if ((int32_t)pAgnosticSchema[iSchema].ILOffset != pSchema[iSchema].ILOffset) + { + LogError("AllocPgoInstrumentationBySchema mismatch: [%d].ILOffset record %d, replay %d", + iSchema, pAgnosticSchema[iSchema].ILOffset, (DWORD)pSchema[iSchema].ILOffset); + } + if ((int32_t)pAgnosticSchema[iSchema].Count != pSchema[iSchema].Count) + { + LogError("AllocPgoInstrumentationBySchema mismatch: [%d].Count record %d, replay %d", + iSchema, pAgnosticSchema[iSchema].Count, (DWORD)pSchema[iSchema].Count); + } + if ((int32_t)pAgnosticSchema[iSchema].Other != pSchema[iSchema].Other) + { + LogError("AllocPgoInstrumentationBySchema mismatch: [%d].Other record %d, replay %d", + iSchema, pAgnosticSchema[iSchema].Other, (DWORD)pSchema[iSchema].Other); + } + pSchema[iSchema].Offset = (size_t)pAgnosticSchema[iSchema].Offset; + if (pSchema[iSchema].Offset > maxOffset) maxOffset = pSchema[iSchema].Offset; } @@ -5141,8 +5197,8 @@ DWORD MethodContext::repAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ft // // Add 16 bytes of represent writeable space size_t bufSize = maxOffset + 16; - *pInstrumentationData = (BYTE*)AllocJitTempBuffer((unsigned)bufSize); - cr->recAddressMap((void*)value.address, (void*)*pInstrumentationData, (unsigned)bufSize); + *pInstrumentationData = (BYTE*)AllocJitTempBuffer(bufSize); + cr->recAddressMap((void*)value.instrumentationDataAddress, (void*)*pInstrumentationData, (unsigned)bufSize); return result; } @@ -5157,19 +5213,21 @@ void MethodContext::recGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd Agnostic_GetPgoInstrumentationResults value; - value.schemaCount = *pCountSchemaItems; + value.countSchemaItems = *pCountSchemaItems; + ICorJitInfo::PgoInstrumentationSchema* pInSchema = *pSchema; Agnostic_PgoInstrumentationSchema* agnosticSchema = (Agnostic_PgoInstrumentationSchema*)malloc(sizeof(Agnostic_PgoInstrumentationSchema) * (*pCountSchemaItems)); size_t maxOffset = 0; for (UINT32 i = 0; i < (*pCountSchemaItems); i++) { - if ((*pSchema)[i].Offset > maxOffset) - maxOffset = (*pSchema)[i].Offset; - agnosticSchema[i].Offset = (*pSchema)[i].Offset; - agnosticSchema[i].InstrumentationKind = (*pSchema)[i].InstrumentationKind; - agnosticSchema[i].ILOffset = (*pSchema)[i].ILOffset; - agnosticSchema[i].Count = (*pSchema)[i].Count; - agnosticSchema[i].Other = (*pSchema)[i].Other; + if (pInSchema[i].Offset > maxOffset) + maxOffset = pInSchema[i].Offset; + + agnosticSchema[i].Offset = (DWORDLONG)pInSchema[i].Offset; + agnosticSchema[i].InstrumentationKind = (DWORD)pInSchema[i].InstrumentationKind; + agnosticSchema[i].ILOffset = (DWORD)pInSchema[i].ILOffset; + agnosticSchema[i].Count = (DWORD)pInSchema[i].Count; + agnosticSchema[i].Other = (DWORD)pInSchema[i].Other; } value.schema_index = GetPgoInstrumentationResults->AddBuffer((unsigned char*)agnosticSchema, sizeof(Agnostic_PgoInstrumentationSchema) * (*pCountSchemaItems)); free(agnosticSchema); @@ -5177,49 +5235,59 @@ void MethodContext::recGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd // This isn't strictly accurate, but I think it'll do size_t bufSize = maxOffset + 16; - value.data_index = GetPgoInstrumentationResults->AddBuffer((unsigned char*)*pInstrumentationData, (unsigned)bufSize); + value.data_index = GetPgoInstrumentationResults->AddBuffer((unsigned char*)*pInstrumentationData, (unsigned)bufSize); value.dataByteCount = (unsigned)bufSize; - value.result = (DWORD)result; + value.result = (DWORD)result; GetPgoInstrumentationResults->Add(CastHandle(ftnHnd), value); } void MethodContext::dmpGetPgoInstrumentationResults(DWORDLONG key, const Agnostic_GetPgoInstrumentationResults& value) { - printf("GetMethodBlockCounts key ftn-%016llX, value schemaCnt-%u profileBufSize-%u", key, value.schemaCount, value.dataByteCount); - Agnostic_PgoInstrumentationSchema* pBuf = - (Agnostic_PgoInstrumentationSchema*)GetPgoInstrumentationResults->GetBuffer(value.schema_index); + printf("GetPgoInstrumentationResults key ftn-%016llX, value res-%08X schemaCnt-%u profileBufSize-%u schema{", + key, value.result, value.countSchemaItems, value.dataByteCount); - for (UINT32 i = 0; i < value.schemaCount; i++) + if (value.countSchemaItems > 0) { - printf(" Offset %016llX ILOffset %u Kind %u Count %u Other %u\n", pBuf[i].Offset, pBuf[i].ILOffset, pBuf[i].InstrumentationKind, pBuf[i].Count, pBuf[i].Other); - } + Agnostic_PgoInstrumentationSchema* pBuf = + (Agnostic_PgoInstrumentationSchema*)GetPgoInstrumentationResults->GetBuffer(value.schema_index); - // TODO, dump actual count data + printf("\n"); + for (DWORD i = 0; i < value.countSchemaItems; i++) + { + printf(" %u-{Offset %016llX ILOffset %u Kind %u(0x%x) Count %u Other %u}\n", + i, pBuf[i].Offset, pBuf[i].ILOffset, pBuf[i].InstrumentationKind, pBuf[i].InstrumentationKind, pBuf[i].Count, pBuf[i].Other); + } + } + printf("} data_index-%u [TODO, dump actual count data]", value.data_index); } -DWORD MethodContext::repGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, +HRESULT MethodContext::repGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, UINT32* pCountSchemaItems, BYTE** pInstrumentationData) { - Agnostic_GetPgoInstrumentationResults tempValue; + AssertCodeMsg(GetPgoInstrumentationResults != nullptr, EXCEPTIONCODE_MC, "Found null GetPgoInstrumentationResults for %016llX", CastHandle(ftnHnd)); + AssertCodeMsg(GetPgoInstrumentationResults->GetIndex(CastHandle(ftnHnd)) != -1, EXCEPTIONCODE_MC, "GetPgoInstrumentationResults: Didn't find %016llX", CastHandle(ftnHnd)); + Agnostic_GetPgoInstrumentationResults tempValue; tempValue = GetPgoInstrumentationResults->Get(CastHandle(ftnHnd)); - *pCountSchemaItems = (UINT32)tempValue.schemaCount; - *pInstrumentationData = (BYTE*)GetPgoInstrumentationResults->GetBuffer(tempValue.data_index); + *pCountSchemaItems = (UINT32)tempValue.countSchemaItems; + *pInstrumentationData = (BYTE*)GetPgoInstrumentationResults->GetBuffer(tempValue.data_index); - *pSchema = (ICorJitInfo::PgoInstrumentationSchema*)AllocJitTempBuffer(tempValue.schemaCount * sizeof(ICorJitInfo::PgoInstrumentationSchema)); + ICorJitInfo::PgoInstrumentationSchema* pOutSchema = (ICorJitInfo::PgoInstrumentationSchema*)AllocJitTempBuffer(tempValue.countSchemaItems * sizeof(ICorJitInfo::PgoInstrumentationSchema)); Agnostic_PgoInstrumentationSchema* pAgnosticSchema = (Agnostic_PgoInstrumentationSchema*)GetPgoInstrumentationResults->GetBuffer(tempValue.schema_index); - for (UINT32 iSchema = 0; iSchema < tempValue.schemaCount; iSchema++) + for (UINT32 iSchema = 0; iSchema < tempValue.countSchemaItems; iSchema++) { - (*pSchema)[iSchema].Offset = (size_t)pAgnosticSchema[iSchema].Offset; - (*pSchema)[iSchema].ILOffset = pAgnosticSchema[iSchema].ILOffset; - (*pSchema)[iSchema].InstrumentationKind = pAgnosticSchema[iSchema].InstrumentationKind; - (*pSchema)[iSchema].Count = pAgnosticSchema[iSchema].Count; - (*pSchema)[iSchema].Other = pAgnosticSchema[iSchema].Other; + pOutSchema[iSchema].Offset = (size_t)pAgnosticSchema[iSchema].Offset; + pOutSchema[iSchema].InstrumentationKind = (ICorJitInfo::PgoInstrumentationKind)pAgnosticSchema[iSchema].InstrumentationKind; + pOutSchema[iSchema].ILOffset = (int32_t)pAgnosticSchema[iSchema].ILOffset; + pOutSchema[iSchema].Count = (int32_t)pAgnosticSchema[iSchema].Count; + pOutSchema[iSchema].Other = (int32_t)pAgnosticSchema[iSchema].Other; } + *pSchema = pOutSchema; + HRESULT result = (HRESULT)tempValue.result; return result; } diff --git a/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.h b/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.h index 30f5533d498fb..e3796060ed344 100644 --- a/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.h +++ b/src/coreclr/ToolBox/superpmi/superpmi-shared/methodcontext.h @@ -637,11 +637,11 @@ class MethodContext void recAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, UINT32 countSchemaItems, BYTE** pInstrumentationData, HRESULT result); void dmpAllocPgoInstrumentationBySchema(DWORDLONG key, const Agnostic_AllocPgoInstrumentationBySchema& value); - DWORD repAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, UINT32 countSchemaItems, BYTE** pInstrumentationData); + HRESULT repAllocPgoInstrumentationBySchema(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema* pSchema, UINT32 countSchemaItems, BYTE** pInstrumentationData); void recGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, UINT32* pCountSchemaItems, BYTE** pInstrumentationData, HRESULT result); void dmpGetPgoInstrumentationResults(DWORDLONG key, const Agnostic_GetPgoInstrumentationResults& value); - DWORD repGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, UINT32* pCountSchemaItems, BYTE** pInstrumentationData); + HRESULT repGetPgoInstrumentationResults(CORINFO_METHOD_HANDLE ftnHnd, ICorJitInfo::PgoInstrumentationSchema** pSchema, UINT32* pCountSchemaItems, BYTE** pInstrumentationData); void recGetLikelyClass(CORINFO_METHOD_HANDLE ftnHnd, CORINFO_CLASS_HANDLE baseHnd, UINT32 ilOffset, CORINFO_CLASS_HANDLE classHnd, UINT32* pLikelihood, UINT32* pNumberOfClasses); void dmpGetLikelyClass(const Agnostic_GetLikelyClass& key, const Agnostic_GetLikelyClassResult& value); @@ -827,7 +827,7 @@ class MethodContext void* AllocJitTempBuffer(size_t size) { DeletionNode *pDeletionNode = (DeletionNode *)malloc(sizeof(DeletionNode) + size); - pDeletionNode = this->nodesToDelete; + pDeletionNode->pNext = this->nodesToDelete; this->nodesToDelete = pDeletionNode; return pDeletionNode + 1; } diff --git a/src/coreclr/classlibnative/bcltype/varargsnative.cpp b/src/coreclr/classlibnative/bcltype/varargsnative.cpp index 55dd48bf280ec..a5f206aeef5de 100644 --- a/src/coreclr/classlibnative/bcltype/varargsnative.cpp +++ b/src/coreclr/classlibnative/bcltype/varargsnative.cpp @@ -21,7 +21,7 @@ // pointer to achieve such an alignment for the next argument on those platforms (otherwise it is a no-op). // NOTE: the debugger has its own implementation of this algorithm in Debug\DI\RsType.cpp, CordbType::RequiresAlign8() // so if you change this implementation be sure to update the debugger's version as well. -static void AdjustArgPtrForAlignment(VARARGS *pData, size_t cbArg) +static void AdjustArgPtrForAlignment(VARARGS *pData, unsigned cbArg) { #ifdef TARGET_ARM // Only 64-bit primitives or value types with embedded 64-bit primitives are aligned on 64-bit boundaries. @@ -114,7 +114,9 @@ static void InitCommon(VARARGS *data, VASigCookie** cookie) // which is the start of the first fixed arg (arg1). // Always skip over the varargs_cookie. - data->ArgPtr += StackElemSize(sizeof(LPVOID)); + const bool isValueType = false; + const bool isFloatHfa = false; + data->ArgPtr += StackElemSize(TARGET_POINTER_SIZE, isValueType, isFloatHfa); #endif } @@ -138,9 +140,11 @@ void AdvanceArgPtr(VARARGS *data) break; SigTypeContext typeContext; // This is an empty type context. This is OK because the vararg methods may not be generic - SIZE_T cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule, &typeContext); - SIZE_T cbArg = StackElemSize(cbRaw); - + TypeHandle thValueType; + const unsigned cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule, &typeContext, &thValueType); + const bool isValueType = (!thValueType.IsNull() && thValueType.IsValueType()); + const bool isFloatHfa = false; + unsigned cbArg = StackElemSize(cbRaw, isValueType, isFloatHfa); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE if (ArgIterator::IsVarArgPassedByRef(cbRaw)) cbArg = sizeof(void*); @@ -263,9 +267,11 @@ VarArgsNative::Init2, } SigTypeContext typeContext; // This is an empty type context. This is OK because the vararg methods may not be generic - SIZE_T cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule,&typeContext); - SIZE_T cbArg = StackElemSize(cbRaw); - + TypeHandle thValueType; + unsigned cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule,&typeContext, &thValueType); + const bool isValueType = (!thValueType.IsNull() && thValueType.IsValueType()); + const bool isFloatHfa = false; + unsigned cbArg = StackElemSize(cbRaw, isValueType, isFloatHfa); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE if (ArgIterator::IsVarArgPassedByRef(cbRaw)) cbArg = sizeof(void*); @@ -401,7 +407,8 @@ FCIMPL3(void, VarArgsNative::GetNextArg2, VARARGS* _this, void * value, ReflectC TypeHandle typehandle = refType->GetType(); _ASSERTE(_this != NULL); - UINT size = 0; + unsigned size = 0; + bool isValueType = false; CorElementType typ = typehandle.GetInternalCorElementType(); if (CorTypeInfo::IsPrimitiveType(typ)) @@ -414,15 +421,15 @@ FCIMPL3(void, VarArgsNative::GetNextArg2, VARARGS* _this, void * value, ReflectC } else if (typ == ELEMENT_TYPE_VALUETYPE) { + isValueType = true; size = typehandle.AsMethodTable()->GetNativeSize(); } else { COMPlusThrow(kNotSupportedException, W("NotSupported_Type")); } - - size = StackElemSize(size); - + const bool isFloatHfa = false; + size = StackElemSize(size, isValueType, isFloatHfa); AdjustArgPtrForAlignment(_this, size); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE @@ -472,9 +479,11 @@ VarArgsNative::GetNextArgHelper( _ASSERTE(data->RemainingArgs != 0); SigTypeContext typeContext; // This is an empty type context. This is OK because the vararg methods may not be generic - SIZE_T cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule,&typeContext); - SIZE_T cbArg = StackElemSize(cbRaw); - + TypeHandle thValueType; + const unsigned cbRaw = data->SigPtr.SizeOf(data->ArgCookie->pModule,&typeContext, &thValueType); + const bool isValueType = (!thValueType.IsNull() && thValueType.IsValueType()); + const bool isFloatHfa = false; + unsigned cbArg = StackElemSize(cbRaw, isValueType, isFloatHfa); AdjustArgPtrForAlignment(data, cbArg); // Get a pointer to the beginning of the argument. diff --git a/src/coreclr/clrfeatures.cmake b/src/coreclr/clrfeatures.cmake index 1687bc50568fc..923c1d92fdfe5 100644 --- a/src/coreclr/clrfeatures.cmake +++ b/src/coreclr/clrfeatures.cmake @@ -29,7 +29,5 @@ if(NOT DEFINED FEATURE_AUTO_TRACE) endif(NOT DEFINED FEATURE_AUTO_TRACE) if(NOT DEFINED FEATURE_SINGLE_FILE_DIAGNOSTICS) - if(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_WIN32) - set(FEATURE_SINGLE_FILE_DIAGNOSTICS 1) - endif(CLR_CMAKE_HOST_LINUX OR CLR_CMAKE_HOST_WIN32) + set(FEATURE_SINGLE_FILE_DIAGNOSTICS 1) endif(NOT DEFINED FEATURE_SINGLE_FILE_DIAGNOSTICS) diff --git a/src/coreclr/debug/runtimeinfo/CMakeLists.txt b/src/coreclr/debug/runtimeinfo/CMakeLists.txt index 5f5778d1899ed..a7811d6ce2c2e 100644 --- a/src/coreclr/debug/runtimeinfo/CMakeLists.txt +++ b/src/coreclr/debug/runtimeinfo/CMakeLists.txt @@ -11,4 +11,7 @@ add_dependencies(runtimeinfo coreclr_module_index_header) if (NOT (CLR_CMAKE_TARGET_WIN32 AND (CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TARGET_ARCH_ARM) AND CLR_CMAKE_HOST_ARCH_AMD64)) add_dependencies(runtimeinfo mscordaccore_module_index_header) add_dependencies(runtimeinfo mscordbi_module_index_header) -endif() \ No newline at end of file +endif() + +# publish runtimeinfo lib +_install(TARGETS runtimeinfo DESTINATION lib) diff --git a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt index 0d5095b567667..2a39c5740a0a9 100644 --- a/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt +++ b/src/coreclr/dlls/mscoree/coreclr/CMakeLists.txt @@ -74,8 +74,6 @@ add_custom_target(coreclr_def DEPENDS ${DEF_FILE}) add_dependencies(coreclr coreclr_def) add_dependencies(coreclr coreclr_exports) -add_dependencies(coreclr_static coreclr_def) -add_dependencies(coreclr_static coreclr_exports) set_property(TARGET coreclr APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION}) set_property(TARGET coreclr APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE}) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index daf36702fe1b6..762b31ab3ef81 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -23108,11 +23108,13 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) // null out the target of long weakref that were not promoted. GCScan::GcWeakPtrScan (GCHeap::Promote, condemned_gen_number, max_generation, &sc); -#if defined(MULTIPLE_HEAPS) && defined(MARK_LIST) +#ifdef MULTIPLE_HEAPS +#ifdef MARK_LIST size_t total_mark_list_size = sort_mark_list(); // first thread to finish sorting will scan the sync syncblk cache +#endif //MARK_LIST if ((syncblock_scan_p == 0) && (Interlocked::Increment(&syncblock_scan_p) == 1)) -#endif //MULTIPLE_HEAPS && MARK_LIST +#endif //MULTIPLE_HEAPS { // scan for deleted entries in the syncblk cache GCScan::GcWeakPtrScanBySingleThread(condemned_gen_number, max_generation, &sc); diff --git a/src/coreclr/gc/unix/configure.cmake b/src/coreclr/gc/unix/configure.cmake index 02c4725e538ba..05f66ae4fbd3c 100644 --- a/src/coreclr/gc/unix/configure.cmake +++ b/src/coreclr/gc/unix/configure.cmake @@ -91,7 +91,12 @@ check_library_exists(${PTHREAD_LIBRARY} pthread_setaffinity_np "" HAVE_PTHREAD_S check_cxx_symbol_exists(_SC_PHYS_PAGES unistd.h HAVE__SC_PHYS_PAGES) check_cxx_symbol_exists(_SC_AVPHYS_PAGES unistd.h HAVE__SC_AVPHYS_PAGES) check_cxx_symbol_exists(swapctl sys/swap.h HAVE_SWAPCTL) -check_function_exists(sysctl HAVE_SYSCTL) +if(CLR_CMAKE_TARGET_LINUX) + # sysctl is deprecated on Linux + set(HAVE_SYSCTL 0) +else() + check_function_exists(sysctl HAVE_SYSCTL) +endif() check_function_exists(sysinfo HAVE_SYSINFO) check_function_exists(sysconf HAVE_SYSCONF) check_struct_has_member ("struct sysinfo" mem_unit "sys/sysinfo.h" HAVE_SYSINFO_WITH_MEM_UNIT) diff --git a/src/coreclr/gcinfo/gcinfoencoder.cpp b/src/coreclr/gcinfo/gcinfoencoder.cpp index b906c3e147a74..8f56607e22ba5 100644 --- a/src/coreclr/gcinfo/gcinfoencoder.cpp +++ b/src/coreclr/gcinfo/gcinfoencoder.cpp @@ -577,8 +577,10 @@ GcSlotId GcInfoEncoder::GetStackSlotId( INT32 spOffset, GcSlotFlags flags, GcSta _ASSERTE( (flags & (GC_SLOT_IS_REGISTER | GC_SLOT_IS_DELETED)) == 0 ); +#if !defined(OSX_ARM64_ABI) // the spOffset for the stack slot is required to be pointer size aligned _ASSERTE((spOffset % TARGET_POINTER_SIZE) == 0); +#endif m_SlotTable[ m_NumSlots ].Slot.Stack.SpOffset = spOffset; m_SlotTable[ m_NumSlots ].Slot.Stack.Base = spBase; diff --git a/src/coreclr/inc/corinfo.h b/src/coreclr/inc/corinfo.h index 8bd277c27dc9f..bdaae65d65ff4 100644 --- a/src/coreclr/inc/corinfo.h +++ b/src/coreclr/inc/corinfo.h @@ -582,8 +582,10 @@ enum CorInfoHelpFunc CORINFO_HELP_JIT_PINVOKE_BEGIN, // Transition to preemptive mode before a P/Invoke, frame is the first argument CORINFO_HELP_JIT_PINVOKE_END, // Transition to cooperative mode after a P/Invoke, frame is the first argument - CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS, // Transition to cooperative mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, // Transition to preemptive mode in reverse P/Invoke epilog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, // Transition to preemptive mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_GVMLOOKUP_FOR_SLOT, // Resolve a generic virtual method target from this pointer and runtime method handle @@ -700,12 +702,16 @@ enum class CorInfoCallConvExtension // New calling conventions supported with the extensible calling convention encoding go here. }; -#ifdef UNIX_X86_ABI +#ifdef TARGET_X86 inline bool IsCallerPop(CorInfoCallConvExtension callConv) { +#ifdef UNIX_X86_ABI return callConv == CorInfoCallConvExtension::Managed || callConv == CorInfoCallConvExtension::C; -} +#else + return callConv == CorInfoCallConvExtension::C; #endif // UNIX_X86_ABI +} +#endif // Determines whether or not this calling convention is an instance method calling convention. inline bool callConvIsInstanceMethodCallConv(CorInfoCallConvExtension callConv) diff --git a/src/coreclr/inc/corjit.h b/src/coreclr/inc/corjit.h index 19d6b3ab028c2..d2c5dd1bcdaa0 100644 --- a/src/coreclr/inc/corjit.h +++ b/src/coreclr/inc/corjit.h @@ -319,9 +319,11 @@ class ICorJitInfo : public ICorDynamicInfo // of the buffer is the same as the format the JIT passes to allocPgoInstrumentationBySchema. virtual HRESULT getPgoInstrumentationResults( CORINFO_METHOD_HANDLE ftnHnd, - PgoInstrumentationSchema **pSchema, // pointer to the schema table which describes the instrumentation results (pointer will not remain valid after jit completes) - UINT32 * pCountSchemaItems, // pointer to the count schema items - BYTE ** pInstrumentationData // pointer to the actual instrumentation data (pointer will not remain valid after jit completes) + PgoInstrumentationSchema **pSchema, // OUT: pointer to the schema table (array) which describes the instrumentation results + // (pointer will not remain valid after jit completes). + UINT32 * pCountSchemaItems, // OUT: pointer to the count of schema items in `pSchema` array. + BYTE ** pInstrumentationData // OUT: `*pInstrumentationData` is set to the address of the instrumentation data + // (pointer will not remain valid after jit completes). ) = 0; // Allocate a profile buffer for use in the current process @@ -335,11 +337,12 @@ class ICorJitInfo : public ICorDynamicInfo // // The intention here is that it becomes possible to describe a C data structure with the alignment for ease of use with // instrumentation helper functions - virtual HRESULT allocPgoInstrumentationBySchema ( + virtual HRESULT allocPgoInstrumentationBySchema( CORINFO_METHOD_HANDLE ftnHnd, - PgoInstrumentationSchema *pSchema, // pointer to the schema table which describes the instrumentation results - UINT32 countSchemaItems, // pointer to the count schema items - BYTE ** pInstrumentationData // pointer to the actual instrumentation data + PgoInstrumentationSchema *pSchema, // IN OUT: pointer to the schema table (array) which describes the instrumentation results. `Offset` field + // is filled in by VM; other fields are set and passed in by caller. + UINT32 countSchemaItems, // IN: count of schema items in `pSchema` array. + BYTE ** pInstrumentationData // OUT: `*pInstrumentationData` is set to the address of the instrumentation data. ) = 0; // Get the likely implementing class for a virtual call or interface call made by ftnHnd diff --git a/src/coreclr/inc/corjitflags.h b/src/coreclr/inc/corjitflags.h index 5cea8a224c609..a29ec24afe61a 100644 --- a/src/coreclr/inc/corjitflags.h +++ b/src/coreclr/inc/corjitflags.h @@ -85,7 +85,7 @@ class CORJIT_FLAGS CORJIT_FLAG_SAMPLING_JIT_BACKGROUND = 35, // JIT is being invoked as a result of stack sampling for hot methods in the background CORJIT_FLAG_USE_PINVOKE_HELPERS = 36, // The JIT should use the PINVOKE_{BEGIN,END} helpers instead of emitting inline transitions CORJIT_FLAG_REVERSE_PINVOKE = 37, // The JIT should insert REVERSE_PINVOKE_{ENTER,EXIT} helpers into method prolog/epilog - CORJIT_FLAG_UNUSED14 = 38, + CORJIT_FLAG_TRACK_TRANSITIONS = 38, // The JIT should insert the REVERSE_PINVOKE helper variants that track transitions. CORJIT_FLAG_TIER0 = 39, // This is the initial tier for tiered compilation which should generate code as quickly as possible CORJIT_FLAG_TIER1 = 40, // This is the final tier (for now) for tiered compilation which should generate high quality code diff --git a/src/coreclr/inc/jiteeversionguid.h b/src/coreclr/inc/jiteeversionguid.h index 12c5abdf7f2c2..69d85eda196d5 100644 --- a/src/coreclr/inc/jiteeversionguid.h +++ b/src/coreclr/inc/jiteeversionguid.h @@ -12,7 +12,7 @@ // be changed. This is the identifier verified by ICorJitCompiler::getVersionIdentifier(). // // You can use "uuidgen.exe -s" to generate this value. -// +// // Note that this file is parsed by some tools, namely superpmi.py, so make sure the first line is exactly // of the form: // @@ -30,12 +30,13 @@ // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE // ////////////////////////////////////////////////////////////////////////////////////////////////////////// +// -constexpr GUID JITEEVersionIdentifier = { /* f556df6c-b9c7-479c-b895-8e1f1959fe59 */ - 0xf556df6c, - 0xb9c7, - 0x479c, - {0xb8, 0x95, 0x8e, 0x1f, 0x19, 0x59, 0xfe, 0x59} +constexpr GUID JITEEVersionIdentifier = { /* 000b3acb-92d2-4003-8760-e545241dd9a8 */ + 0x000b3acb, + 0x92d2, + 0x4003, + {0x87, 0x60, 0xe5, 0x45, 0x24, 0x1d, 0xd9, 0xa8} }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/coreclr/inc/jithelpers.h b/src/coreclr/inc/jithelpers.h index 38f518251cf5d..3f72fdbc8ccc4 100644 --- a/src/coreclr/inc/jithelpers.h +++ b/src/coreclr/inc/jithelpers.h @@ -343,8 +343,10 @@ JITHELPER(CORINFO_HELP_JIT_PINVOKE_BEGIN, JIT_PInvokeBegin, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_JIT_PINVOKE_END, JIT_PInvokeEnd, CORINFO_HELP_SIG_REG_ONLY) - JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, JIT_ReversePInvokeEnter, CORINFO_HELP_SIG_REG_ONLY) - JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, JIT_ReversePInvokeExit, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, JIT_ReversePInvokeEnter, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS, JIT_ReversePInvokeEnterTrackTransitions, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, JIT_ReversePInvokeExit, CORINFO_HELP_SIG_REG_ONLY) + JITHELPER(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, JIT_ReversePInvokeExitTrackTransitions, CORINFO_HELP_SIG_REG_ONLY) JITHELPER(CORINFO_HELP_GVMLOOKUP_FOR_SLOT, NULL, CORINFO_HELP_SIG_NO_ALIGN_STUB) diff --git a/src/coreclr/inc/readytorun.h b/src/coreclr/inc/readytorun.h index 4b68acba0cdc8..e2746b97a7e4b 100644 --- a/src/coreclr/inc/readytorun.h +++ b/src/coreclr/inc/readytorun.h @@ -397,7 +397,11 @@ struct READYTORUN_EXCEPTION_CLAUSE enum ReadyToRunRuntimeConstants : DWORD { READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11, - READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2 +#ifdef TARGET_X86 + READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 5, +#else + READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2, +#endif }; enum ReadyToRunHFAElemType : DWORD diff --git a/src/coreclr/inc/stdmacros.h b/src/coreclr/inc/stdmacros.h index 6f1f884b2c7ac..f2417c7637545 100644 --- a/src/coreclr/inc/stdmacros.h +++ b/src/coreclr/inc/stdmacros.h @@ -185,17 +185,11 @@ inline size_t ALIGN_UP( size_t val, size_t alignment ) _ASSERTE( result >= val ); // check for overflow return result; } -inline void* ALIGN_UP( void* val, size_t alignment ) -{ - WRAPPER_NO_CONTRACT; - return (void*) ALIGN_UP( (size_t)val, alignment ); -} -inline uint8_t* ALIGN_UP( uint8_t* val, size_t alignment ) +template inline T ALIGN_UP(T val, size_t alignment) { WRAPPER_NO_CONTRACT; - - return (uint8_t*) ALIGN_UP( (size_t)val, alignment ); + return (T)ALIGN_UP((size_t)val, alignment); } inline size_t ALIGN_DOWN( size_t val, size_t alignment ) diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index f39472778cdb9..56c7b65914661 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -700,12 +700,6 @@ HRESULT NativeObjectWrapperContext::Create( { _ASSERTE(external != nullptr && context != nullptr); - // Aggregated inners are only currently supported for Aggregated - // scenarios involving IReferenceTracker. - _ASSERTE(inner == nullptr - || ((flags & InteropLib::Com::CreateObjectFlags_TrackerObject) - && (flags & InteropLib::Com::CreateObjectFlags_Aggregated))); - HRESULT hr; ComHolder trackerObject; diff --git a/src/coreclr/interop/inc/interoplib.h b/src/coreclr/interop/inc/interoplib.h index 39ceadfbb809c..96f157929e0a5 100644 --- a/src/coreclr/interop/inc/interoplib.h +++ b/src/coreclr/interop/inc/interoplib.h @@ -67,13 +67,15 @@ namespace InteropLib CreateObjectFlags_TrackerObject = 1, CreateObjectFlags_UniqueInstance = 2, CreateObjectFlags_Aggregated = 4, + CreateObjectFlags_Unwrap = 8, }; - // Get the true identity for the supplied IUnknown. - HRESULT GetIdentityForCreateWrapperForExternal( + // Get the true identity and inner for the supplied IUnknown. + HRESULT DetermineIdentityAndInnerForExternal( _In_ IUnknown* external, _In_ enum CreateObjectFlags flags, - _Outptr_ IUnknown** identity) noexcept; + _Outptr_ IUnknown** identity, + _Inout_ IUnknown** innerMaybe) noexcept; // Allocate a wrapper context for an external object. // The runtime supplies the external object, flags, and a memory diff --git a/src/coreclr/interop/interoplib.cpp b/src/coreclr/interop/interoplib.cpp index 9aff8c2bb335c..1dc83abc1f00c 100644 --- a/src/coreclr/interop/interoplib.cpp +++ b/src/coreclr/interop/interoplib.cpp @@ -73,8 +73,6 @@ namespace InteropLib if (mow == nullptr) return E_INVALIDARG; - (void)mow->AddRef(); - *object = mow->Target; return S_OK; } @@ -98,12 +96,13 @@ namespace InteropLib return wrapper->IsSet(CreateComInterfaceFlagsEx::IsComActivated) ? S_OK : S_FALSE; } - HRESULT GetIdentityForCreateWrapperForExternal( + HRESULT DetermineIdentityAndInnerForExternal( _In_ IUnknown* external, _In_ enum CreateObjectFlags flags, - _Outptr_ IUnknown** identity) noexcept + _Outptr_ IUnknown** identity, + _Inout_ IUnknown** innerMaybe) noexcept { - _ASSERTE(external != nullptr && identity != nullptr); + _ASSERTE(external != nullptr && identity != nullptr && innerMaybe != nullptr); IUnknown* checkForIdentity = external; @@ -129,7 +128,22 @@ namespace InteropLib checkForIdentity = trackerObject.p; } - return checkForIdentity->QueryInterface(identity); + HRESULT hr; + + IUnknown* identityLocal; + RETURN_IF_FAILED(checkForIdentity->QueryInterface(&identityLocal)); + + // Set the inner if scenario dictates an update. + if (*innerMaybe == nullptr // User didn't supply inner - .NET 5 API scenario sanity check. + && checkForIdentity != external // Target of check was changed - .NET 5 API scenario sanity check. + && external != identityLocal // The supplied object doesn't match the computed identity. + && refTrackerInnerScenario) // The appropriate flags were set. + { + *innerMaybe = external; + } + + *identity = identityLocal; + return S_OK; } HRESULT CreateWrapperForExternal( diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 8c4572dcec43f..3933ace5f345f 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -8896,10 +8896,8 @@ void CodeGen::genFnEpilog(BasicBlock* block) if (compiler->info.compIsVarArgs) fCalleePop = false; -#ifdef UNIX_X86_ABI if (IsCallerPop(compiler->info.compCallConv)) fCalleePop = false; -#endif // UNIX_X86_ABI if (fCalleePop) { diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 60bfd6b615da7..5611a018ac89b 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -1129,6 +1129,7 @@ void CodeGen::genCodeForMul(GenTreeOp* treeNode) } #ifdef FEATURE_SIMD + //------------------------------------------------------------------------ // genSIMDSplitReturn: Generates code for returning a fixed-size SIMD type that lives // in a single register, but is returned in multiple registers. @@ -1148,34 +1149,62 @@ void CodeGen::genSIMDSplitReturn(GenTree* src, ReturnTypeDesc* retTypeDesc) regNumber reg0 = retTypeDesc->GetABIReturnReg(0); regNumber reg1 = retTypeDesc->GetABIReturnReg(1); + assert((reg0 != REG_NA) && (reg1 != REG_NA) && (opReg != REG_NA)); + + const bool srcIsFloatReg = genIsValidFloatReg(opReg); + const bool dstIsFloatReg = genIsValidFloatReg(reg0); + assert(srcIsFloatReg); + +#ifdef TARGET_AMD64 + assert(src->TypeIs(TYP_SIMD16)); + assert(srcIsFloatReg == dstIsFloatReg); if (opReg != reg0 && opReg != reg1) { // Operand reg is different from return regs. // Copy opReg to reg0 and let it to be handled by one of the // two cases below. - inst_RV_RV(ins_Copy(TYP_DOUBLE), reg0, opReg, TYP_DOUBLE); + inst_RV_RV(ins_Copy(opReg, TYP_SIMD16), reg0, opReg, TYP_SIMD16); opReg = reg0; } if (opReg == reg0) { assert(opReg != reg1); - - // reg0 - already has required 8-byte in bit position [63:0]. // reg1 = opReg. - // swap upper and lower 8-bytes of reg1 so that desired 8-byte is in bit position [63:0]. - inst_RV_RV(ins_Copy(TYP_DOUBLE), reg1, opReg, TYP_DOUBLE); + inst_RV_RV(ins_Copy(opReg, TYP_SIMD16), reg1, opReg, TYP_SIMD16); } else { assert(opReg == reg1); // reg0 = opReg. - // swap upper and lower 8-bytes of reg1 so that desired 8-byte is in bit position [63:0]. - inst_RV_RV(ins_Copy(TYP_DOUBLE), reg0, opReg, TYP_DOUBLE); + + inst_RV_RV(ins_Copy(opReg, TYP_SIMD16), reg0, opReg, TYP_SIMD16); } + // reg0 - already has required 8-byte in bit position [63:0]. + // swap upper and lower 8-bytes of reg1 so that desired 8-byte is in bit position [63:0]. inst_RV_RV_IV(INS_shufpd, EA_16BYTE, reg1, reg1, 0x01); + +#else // TARGET_X86 + assert(src->TypeIs(TYP_SIMD8)); + assert(srcIsFloatReg != dstIsFloatReg); + assert((reg0 == REG_EAX) && (reg1 == REG_EDX)); + // reg0 = opReg[31:0] + inst_RV_RV(ins_Copy(opReg, TYP_INT), reg0, opReg, TYP_INT); + // reg1 = opRef[61:32] + if (compiler->compOpportunisticallyDependsOn(InstructionSet_SSE41)) + { + inst_RV_TT_IV(INS_pextrd, EA_4BYTE, reg1, src, 1); + } + else + { + int8_t shuffleMask = 1; // we only need [61:32]->[31:0], the rest is not read. + inst_RV_TT_IV(INS_pshufd, EA_8BYTE, opReg, src, shuffleMask); + inst_RV_RV(ins_Copy(opReg, TYP_INT), reg1, opReg, TYP_INT); + } +#endif // TARGET_X86 } + #endif // FEATURE_SIMD #if defined(TARGET_X86) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 2843a57c4adce..efdf66d864b35 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -2600,6 +2600,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_EnC)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_DEBUG_INFO)); assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_REVERSE_PINVOKE)); + assert(!jitFlags->IsSet(JitFlags::JIT_FLAG_TRACK_TRANSITIONS)); } opts.jitFlags = jitFlags; @@ -2903,7 +2904,7 @@ void Compiler::compInitOptions(JitFlags* jitFlags) JITDUMP("BBOPT set -- VM query for profile data for %s returned: hr=%0x; schema at %p, counts at %p, %d schema " "elements, %d runs\n", - info.compFullName, hr, fgPgoSchema, fgPgoData, fgPgoSchemaCount, fgNumProfileRuns); + info.compFullName, hr, dspPtr(fgPgoSchema), dspPtr(fgPgoData), fgPgoSchemaCount, fgNumProfileRuns); // a failed result that also has a non-NULL fgPgoSchema // indicates that the ILSize for the method no longer matches @@ -6184,10 +6185,12 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr, { bool unused; info.compCallConv = info.compCompHnd->getUnmanagedCallConv(methodInfo->ftn, nullptr, &unused); + info.compArgOrder = Target::g_tgtUnmanagedArgOrder; } else { info.compCallConv = CorInfoCallConvExtension::Managed; + info.compArgOrder = Target::g_tgtArgOrder; } info.compIsVarArgs = false; diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 68b1d343aa49d..3feabf558b94c 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -9379,6 +9379,8 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // current number of EH clauses (after additions like synchronized // methods and funclets, and removals like unreachable code deletion). + Target::ArgOrder compArgOrder; + bool compMatchedVM; // true if the VM is "matched": either the JIT is a cross-compiler // and the VM expects that, or the JIT is a "self-host" compiler // (e.g., x86 hosted targeting x86) and the VM expects that. @@ -9458,6 +9460,14 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX return (info.compRetBuffArg != BAD_VAR_NUM); } #endif // TARGET_WINDOWS && TARGET_ARM64 + // 4. x86 unmanaged calling conventions require the address of RetBuff to be returned in eax. + CLANG_FORMAT_COMMENT_ANCHOR; +#if defined(TARGET_X86) + if (info.compCallConv != CorInfoCallConvExtension::Managed) + { + return (info.compRetBuffArg != BAD_VAR_NUM); + } +#endif return false; #endif // TARGET_AMD64 diff --git a/src/coreclr/jit/emitxarch.cpp b/src/coreclr/jit/emitxarch.cpp index b6ca4dd7030a3..88e13c4839e21 100644 --- a/src/coreclr/jit/emitxarch.cpp +++ b/src/coreclr/jit/emitxarch.cpp @@ -8821,7 +8821,7 @@ void emitter::emitDispIns( } else if (ins == INS_mov_xmm2i) { - printf("%s, %s", emitRegName(id->idReg2(), attr), emitRegName(id->idReg1(), EA_16BYTE)); + printf("%s, %s", emitRegName(id->idReg1(), attr), emitRegName(id->idReg2(), EA_16BYTE)); } else if (ins == INS_pmovmskb) { diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 950988adca193..2bb6b59a7e3e3 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -8683,13 +8683,42 @@ void Compiler::fgAddReversePInvokeEnterExit() varDsc->lvType = TYP_BLK; varDsc->lvExactSize = eeGetEEInfo()->sizeOfReversePInvokeFrame; + // Add enter pinvoke exit callout at the start of prolog + + GenTree* pInvokeFrameVar = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaReversePInvokeFrameVar, TYP_BLK)); + GenTree* tree; - // Add enter pinvoke exit callout at the start of prolog + CorInfoHelpFunc reversePInvokeEnterHelper; - tree = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaReversePInvokeFrameVar, TYP_BLK)); + GenTreeCall::Use* args; - tree = gtNewHelperCallNode(CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, TYP_VOID, gtNewCallArgs(tree)); + if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TRACK_TRANSITIONS)) + { + reversePInvokeEnterHelper = CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS; + + GenTree* stubArgument; + if (info.compPublishStubParam) + { + // If we have a secret param for a Reverse P/Invoke, that means that we are in an IL stub. + // In this case, the method handle we pass down to the Reverse P/Invoke helper should be + // the target method, which is passed in the secret parameter. + stubArgument = gtNewLclvNode(lvaStubArgumentVar, TYP_I_IMPL); + } + else + { + stubArgument = gtNewIconNode(0, TYP_I_IMPL); + } + + args = gtNewCallArgs(pInvokeFrameVar, gtNewIconEmbMethHndNode(info.compMethodHnd), stubArgument); + } + else + { + reversePInvokeEnterHelper = CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER; + args = gtNewCallArgs(pInvokeFrameVar); + } + + tree = gtNewHelperCallNode(reversePInvokeEnterHelper, TYP_VOID, args); fgEnsureFirstBBisScratch(); @@ -8709,7 +8738,11 @@ void Compiler::fgAddReversePInvokeEnterExit() tree = gtNewOperNode(GT_ADDR, TYP_I_IMPL, gtNewLclvNode(lvaReversePInvokeFrameVar, TYP_BLK)); - tree = gtNewHelperCallNode(CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, TYP_VOID, gtNewCallArgs(tree)); + CorInfoHelpFunc reversePInvokeExitHelper = opts.jitFlags->IsSet(JitFlags::JIT_FLAG_TRACK_TRANSITIONS) + ? CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS + : CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT; + + tree = gtNewHelperCallNode(reversePInvokeExitHelper, TYP_VOID, gtNewCallArgs(tree)); assert(genReturnBB != nullptr); @@ -23639,6 +23672,7 @@ void Compiler::fgInvokeInlineeCompiler(GenTreeCall* call, InlineResult* inlineRe compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_DEBUG_EnC); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_DEBUG_INFO); compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_REVERSE_PINVOKE); + compileFlagsForInlinee.Clear(JitFlags::JIT_FLAG_TRACK_TRANSITIONS); compileFlagsForInlinee.Set(JitFlags::JIT_FLAG_SKIP_VERIFICATION); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 078a745f39f40..180c991148abb 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -13501,6 +13501,12 @@ void Compiler::impImportBlockCode(BasicBlock* block) { op1->gtFlags |= (GTF_OVERFLOW | GTF_EXCEPT); } + + if (op1->gtGetOp1()->OperIsConst() && opts.OptimizationEnabled()) + { + // Try and fold the introduced cast + op1 = gtFoldExprConst(op1); + } } impPushOnStack(op1, tiRetVal); @@ -17334,6 +17340,12 @@ bool Compiler::impReturnInstruction(int prefixFlags, OPCODE& opcode) { op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); } +#endif +#if defined(TARGET_X86) + else if (info.compCallConv != CorInfoCallConvExtension::Managed) + { + op1 = gtNewOperNode(GT_RETURN, TYP_BYREF, gtNewLclvNode(info.compRetBuffArg, TYP_BYREF)); + } #endif else { diff --git a/src/coreclr/jit/jitee.h b/src/coreclr/jit/jitee.h index 6301166e489c0..496da9edfb1cb 100644 --- a/src/coreclr/jit/jitee.h +++ b/src/coreclr/jit/jitee.h @@ -69,7 +69,7 @@ class JitFlags JIT_FLAG_SAMPLING_JIT_BACKGROUND = 35, // JIT is being invoked as a result of stack sampling for hot methods in the background JIT_FLAG_USE_PINVOKE_HELPERS = 36, // The JIT should use the PINVOKE_{BEGIN,END} helpers instead of emitting inline transitions JIT_FLAG_REVERSE_PINVOKE = 37, // The JIT should insert REVERSE_PINVOKE_{ENTER,EXIT} helpers into method prolog/epilog - JIT_FLAG_UNUSED14 = 38, + JIT_FLAG_TRACK_TRANSITIONS = 38, // The JIT should insert the helper variants that track transitions. JIT_FLAG_TIER0 = 39, // This is the initial tier for tiered compilation which should generate code as quickly as possible JIT_FLAG_TIER1 = 40, // This is the final tier (for now) for tiered compilation which should generate high quality code diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index be0f5ccfe0540..a9abde124fc37 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -235,7 +235,29 @@ void Compiler::lvaInitTypeRef() //------------------------------------------------------------------------- InitVarDscInfo varDscInfo; - varDscInfo.Init(lvaTable, hasRetBuffArg); +#ifdef TARGET_X86 + // x86 unmanaged calling conventions limit the number of registers supported + // for accepting arguments. As a result, we need to modify the number of registers + // when we emit a method with an unmanaged calling convention. + switch (info.compCallConv) + { + case CorInfoCallConvExtension::Thiscall: + // In thiscall the this parameter goes into a register. + varDscInfo.Init(lvaTable, hasRetBuffArg, 1, 0); + break; + case CorInfoCallConvExtension::C: + case CorInfoCallConvExtension::Stdcall: + varDscInfo.Init(lvaTable, hasRetBuffArg, 0, 0); + break; + case CorInfoCallConvExtension::Managed: + case CorInfoCallConvExtension::Fastcall: + default: + varDscInfo.Init(lvaTable, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); + break; + } +#else + varDscInfo.Init(lvaTable, hasRetBuffArg, MAX_REG_ARG, MAX_FLOAT_REG_ARG); +#endif lvaInitArgs(&varDscInfo); @@ -513,14 +535,16 @@ void Compiler::lvaInitRetBuffArg(InitVarDscInfo* varDscInfo, bool useFixedRetBuf info.compRetBuffArg = varDscInfo->varNum; varDsc->lvType = TYP_BYREF; varDsc->lvIsParam = 1; - varDsc->lvIsRegArg = 1; + varDsc->lvIsRegArg = 0; if (useFixedRetBufReg && hasFixedRetBuffReg()) { + varDsc->lvIsRegArg = 1; varDsc->SetArgReg(theFixedRetBuffReg()); } - else + else if (varDscInfo->canEnreg(TYP_INT)) { + varDsc->lvIsRegArg = 1; unsigned retBuffArgNum = varDscInfo->allocRegArg(TYP_INT); varDsc->SetArgReg(genMapIntRegArgNumToRegNum(retBuffArgNum)); } @@ -557,10 +581,10 @@ void Compiler::lvaInitRetBuffArg(InitVarDscInfo* varDscInfo, bool useFixedRetBuf } #endif // FEATURE_SIMD - assert(isValidIntArgReg(varDsc->GetArgReg())); + assert(!varDsc->lvIsRegArg || isValidIntArgReg(varDsc->GetArgReg())); #ifdef DEBUG - if (verbose) + if (varDsc->lvIsRegArg && verbose) { printf("'__retBuf' passed in register %s\n", getRegName(varDsc->GetArgReg())); } @@ -591,7 +615,10 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un #if defined(TARGET_X86) // Only (some of) the implicit args are enregistered for varargs - varDscInfo->maxIntRegArgNum = info.compIsVarArgs ? varDscInfo->intRegArgNum : MAX_REG_ARG; + if (info.compIsVarArgs) + { + varDscInfo->maxIntRegArgNum = varDscInfo->intRegArgNum; + } #elif defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI) // On System V type environment the float registers are not indexed together with the int ones. varDscInfo->floatRegArgNum = varDscInfo->intRegArgNum; @@ -5345,7 +5372,7 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() This is all relative to our Virtual '0' */ - if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R) + if (info.compArgOrder == Target::ARG_ORDER_L2R) { argOffs = compArgSize; } @@ -5357,9 +5384,10 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() noway_assert(compArgSize >= codeGen->intRegState.rsCalleeRegArgCount * REGSIZE_BYTES); #endif -#ifdef TARGET_X86 - argOffs -= codeGen->intRegState.rsCalleeRegArgCount * REGSIZE_BYTES; -#endif + if (info.compArgOrder == Target::ARG_ORDER_L2R) + { + argOffs -= codeGen->intRegState.rsCalleeRegArgCount * REGSIZE_BYTES; + } // Update the arg initial register locations. lvaUpdateArgsWithInitialReg(); @@ -5398,11 +5426,8 @@ void Compiler::lvaAssignVirtualFrameOffsetsToArgs() if (info.compRetBuffArg != BAD_VAR_NUM) { noway_assert(lclNum == info.compRetBuffArg); - noway_assert(lvaTable[lclNum].lvIsRegArg); -#ifndef TARGET_X86 argOffs = lvaAssignVirtualFrameOffsetToArg(lclNum, REGSIZE_BYTES, argOffs UNIX_AMD64_ABI_ONLY_ARG(&callerArgOffset)); -#endif // TARGET_X86 lclNum++; } @@ -5553,7 +5578,7 @@ int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum, noway_assert(lclNum < info.compArgsCount); noway_assert(argSize); - if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R) + if (info.compArgOrder == Target::ARG_ORDER_L2R) { argOffs -= argSize; } @@ -5621,7 +5646,7 @@ int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum, } } - if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg) + if (info.compArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg) { argOffs += argSize; } @@ -5646,7 +5671,7 @@ int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum, noway_assert(lclNum < info.compArgsCount); noway_assert(argSize); - if (Target::g_tgtArgOrder == Target::ARG_ORDER_L2R) + if (info.compArgOrder == Target::ARG_ORDER_L2R) { argOffs -= argSize; } @@ -5925,7 +5950,7 @@ int Compiler::lvaAssignVirtualFrameOffsetToArg(unsigned lclNum, } } - if (Target::g_tgtArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg) + if (info.compArgOrder == Target::ARG_ORDER_R2L && !varDsc->lvIsRegArg) { argOffs += argSize; } diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 652b99e60ba82..de515f1206830 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -567,97 +567,6 @@ LsraLocation Referenceable::getNextRefLocation() } } -// Iterate through all the registers of the given type -class RegisterIterator -{ - friend class Registers; - -public: - RegisterIterator(RegisterType type) : regType(type) - { - if (useFloatReg(regType)) - { - currentRegNum = REG_FP_FIRST; - } - else - { - currentRegNum = REG_INT_FIRST; - } - } - -protected: - static RegisterIterator Begin(RegisterType regType) - { - return RegisterIterator(regType); - } - static RegisterIterator End(RegisterType regType) - { - RegisterIterator endIter = RegisterIterator(regType); - // This assumes only integer and floating point register types - // if we target a processor with additional register types, - // this would have to change - if (useFloatReg(regType)) - { - // This just happens to work for both double & float - endIter.currentRegNum = REG_NEXT(REG_FP_LAST); - } - else - { - endIter.currentRegNum = REG_NEXT(REG_INT_LAST); - } - return endIter; - } - -public: - void operator++(int dummy) // int dummy is c++ for "this is postfix ++" - { - currentRegNum = REG_NEXT(currentRegNum); -#ifdef TARGET_ARM - if (regType == TYP_DOUBLE) - currentRegNum = REG_NEXT(currentRegNum); -#endif - } - void operator++() // prefix operator++ - { - currentRegNum = REG_NEXT(currentRegNum); -#ifdef TARGET_ARM - if (regType == TYP_DOUBLE) - currentRegNum = REG_NEXT(currentRegNum); -#endif - } - regNumber operator*() - { - return currentRegNum; - } - bool operator!=(const RegisterIterator& other) - { - return other.currentRegNum != currentRegNum; - } - -private: - regNumber currentRegNum; - RegisterType regType; -}; - -class Registers -{ -public: - friend class RegisterIterator; - RegisterType type; - Registers(RegisterType t) - { - type = t; - } - RegisterIterator begin() - { - return RegisterIterator::Begin(type); - } - RegisterIterator end() - { - return RegisterIterator::End(type); - } -}; - #ifdef DEBUG void LinearScan::dumpVarToRegMap(VarToRegMap map) { @@ -3453,8 +3362,9 @@ regNumber LinearScan::allocateReg(Interval* currentInterval, RefPosition* refPos regNumber farthestCandidateRegNum = genRegNumFromMask(farthestCandidateBit); // Find the next RefPosition of the register. - LsraLocation nextIntervalLocation = nextIntervalRef[farthestCandidateRegNum]; - LsraLocation nextPhysRefLocation = Min(nextFixedRef[farthestCandidateRegNum], nextIntervalLocation); + LsraLocation nextIntervalLocation = + getNextIntervalRef(farthestCandidateRegNum, currentInterval->registerType); + LsraLocation nextPhysRefLocation = Min(nextFixedRef[farthestCandidateRegNum], nextIntervalLocation); if (nextPhysRefLocation == farthestLocation) { farthestSet |= farthestCandidateBit; @@ -3480,22 +3390,70 @@ regNumber LinearScan::allocateReg(Interval* currentInterval, RefPosition* refPos prevRegOptCandidates &= ~prevRegOptCandidateBit; regNumber prevRegOptCandidateRegNum = genRegNumFromMask(prevRegOptCandidateBit); Interval* assignedInterval = physRegs[prevRegOptCandidateRegNum].assignedInterval; - // The assigned should be non-null, and should have a recentRefPosition, however since - // this is a heuristic, we don't want a fatal error, so we just assert (not noway_assert). + bool foundPrevRegOptReg = true; +#ifdef DEBUG + bool hasAssignedInterval = false; +#endif + if ((assignedInterval != nullptr) && (assignedInterval->recentRefPosition != nullptr)) { - if (assignedInterval->recentRefPosition->reload && assignedInterval->recentRefPosition->RegOptional()) + foundPrevRegOptReg &= + (assignedInterval->recentRefPosition->reload && assignedInterval->recentRefPosition->RegOptional()); +#ifdef DEBUG + hasAssignedInterval = true; +#endif + } +#ifndef TARGET_ARM + else + { + foundPrevRegOptReg = false; + } +#endif + +#ifdef TARGET_ARM + // If current interval is TYP_DOUBLE, verify if the other half register matches the heuristics. + // We have three cases: + // 1. One of the register of the pair have an assigned interval: Check if that register's refPosition + // matches the heuristics. If yes, add it to the set. + // 2. Both registers of the pair have an assigned interval: Conservatively "and" conditions for + // heuristics of their corresponding refPositions. If both register's heuristic matches, add them + // to the set. TODO-CQ-ARM: We may implement a better condition later. + // 3. None of the register have an assigned interval: Skip adding register and assert. + if (currentInterval->registerType == TYP_DOUBLE) + { + regNumber anotherHalfRegNum = findAnotherHalfRegNum(prevRegOptCandidateRegNum); + assignedInterval = physRegs[anotherHalfRegNum].assignedInterval; + if ((assignedInterval != nullptr) && (assignedInterval->recentRefPosition != nullptr)) { - // TODO-Cleanup: Previously, we always used the highest regNum with a previous regOptional - // RefPosition, which is not really consistent with the way other selection criteria are applied. - // should probably be: prevRegOptSet |= prevRegOptCandidateBit; - prevRegOptSet = prevRegOptCandidateBit; + if (assignedInterval->recentRefPosition->reload && + assignedInterval->recentRefPosition->RegOptional()) + { + foundPrevRegOptReg &= (assignedInterval->recentRefPosition->reload && + assignedInterval->recentRefPosition->RegOptional()); + } +#ifdef DEBUG + hasAssignedInterval = true; +#endif } } - else +#endif + + if (foundPrevRegOptReg) + { + // TODO-Cleanup: Previously, we always used the highest regNum with a previous regOptional + // RefPosition, which is not really consistent with the way other selection criteria are + // applied. should probably be: prevRegOptSet |= prevRegOptCandidateBit; + prevRegOptSet = prevRegOptCandidateBit; + } + +#ifdef DEBUG + // The assigned should be non-null, and should have a recentRefPosition, however since + // this is a heuristic, we don't want a fatal error, so we just assert (not noway_assert). + if (!hasAssignedInterval) { assert(!"Spill candidate has no assignedInterval recentRefPosition"); } +#endif } found = selector.applySelection(PREV_REG_OPT, prevRegOptSet); } @@ -4592,26 +4550,41 @@ RegRecord* LinearScan::getSecondHalfRegRec(RegRecord* regRec) // RegRecord* LinearScan::findAnotherHalfRegRec(RegRecord* regRec) { - regNumber anotherHalfRegNum; - RegRecord* anotherHalfRegRec; + regNumber anotherHalfRegNum = findAnotherHalfRegNum(regRec->regNum); + return getRegisterRecord(anotherHalfRegNum); +} +//------------------------------------------------------------------------------------------ +// findAnotherHalfRegNum: Find another half register's number which forms same ARM32 double register +// +// Arguments: +// regNumber - A float regNumber +// +// Assumptions: +// None +// +// Return Value: +// A register number which forms same double register with regNum. +// +regNumber LinearScan::findAnotherHalfRegNum(regNumber regNum) +{ + regNumber anotherHalfRegNum; - assert(genIsValidFloatReg(regRec->regNum)); + assert(genIsValidFloatReg(regNum)); // Find another half register for TYP_DOUBLE interval, // following same logic in canRestorePreviousInterval(). - if (genIsValidDoubleReg(regRec->regNum)) + if (genIsValidDoubleReg(regNum)) { - anotherHalfRegNum = REG_NEXT(regRec->regNum); + anotherHalfRegNum = REG_NEXT(regNum); assert(!genIsValidDoubleReg(anotherHalfRegNum)); } else { - anotherHalfRegNum = REG_PREV(regRec->regNum); + anotherHalfRegNum = REG_PREV(regNum); assert(genIsValidDoubleReg(anotherHalfRegNum)); } - anotherHalfRegRec = getRegisterRecord(anotherHalfRegNum); - return anotherHalfRegRec; + return anotherHalfRegNum; } #endif @@ -4934,6 +4907,20 @@ void LinearScan::processBlockStartLocations(BasicBlock* currentBlock) unassignIntervalBlockStart(getSecondHalfRegRec(targetRegRecord), allocationPassComplete ? nullptr : inVarToRegMap); } + + // If this is a TYP_FLOAT interval, and the assigned interval was TYP_DOUBLE, we also + // need to update the liveRegs to specify that the other half is not live anymore. + // As mentioned above, for TYP_DOUBLE, the other half will be unassigned further below. + if ((interval->registerType == TYP_FLOAT) && + ((targetRegRecord->assignedInterval != nullptr) && + (targetRegRecord->assignedInterval->registerType == TYP_DOUBLE))) + { + RegRecord* anotherHalfRegRec = findAnotherHalfRegRec(targetRegRecord); + + // Use TYP_FLOAT to get the regmask of just the half reg. + liveRegs &= ~getRegMask(anotherHalfRegRec->regNum, TYP_FLOAT); + } + #endif // TARGET_ARM unassignIntervalBlockStart(targetRegRecord, allocationPassComplete ? nullptr : inVarToRegMap); assignPhysReg(targetRegRecord, interval); diff --git a/src/coreclr/jit/lsra.h b/src/coreclr/jit/lsra.h index a89a008dd6026..345cbb451f788 100644 --- a/src/coreclr/jit/lsra.h +++ b/src/coreclr/jit/lsra.h @@ -968,6 +968,7 @@ class LinearScan : public LinearScanInterface bool isSecondHalfReg(RegRecord* regRec, Interval* interval); RegRecord* getSecondHalfRegRec(RegRecord* regRec); RegRecord* findAnotherHalfRegRec(RegRecord* regRec); + regNumber findAnotherHalfRegNum(regNumber regNum); bool canSpillDoubleReg(RegRecord* physRegRecord, LsraLocation refLocation); void unassignDoublePhysReg(RegRecord* doubleRegRecord); #endif diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index debbe3b85f01e..9816306ebb6c1 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -12212,7 +12212,12 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac) tree = gtFoldExpr(tree); } - tree->AsOp()->CheckDivideByConstOptimized(this); + // We may fail to fold + if (!tree->OperIsConst()) + { + tree->AsOp()->CheckDivideByConstOptimized(this); + } + return tree; } } diff --git a/src/coreclr/jit/register_arg_convention.h b/src/coreclr/jit/register_arg_convention.h index 7b3199b03af78..a1816ba897e85 100644 --- a/src/coreclr/jit/register_arg_convention.h +++ b/src/coreclr/jit/register_arg_convention.h @@ -33,15 +33,15 @@ struct InitVarDscInfo public: // set to initial values - void Init(LclVarDsc* lvaTable, bool _hasRetBufArg) + void Init(LclVarDsc* lvaTable, bool _hasRetBufArg, unsigned _maxIntRegArgNum, unsigned _maxFloatRegArgNum) { hasRetBufArg = _hasRetBufArg; varDsc = &lvaTable[0]; // the first argument LclVar 0 varNum = 0; // the first argument varNum 0 intRegArgNum = 0; floatRegArgNum = 0; - maxIntRegArgNum = MAX_REG_ARG; - maxFloatRegArgNum = MAX_FLOAT_REG_ARG; + maxIntRegArgNum = _maxIntRegArgNum; + maxFloatRegArgNum = _maxFloatRegArgNum; #ifdef TARGET_ARM fltArgSkippedRegMask = RBM_NONE; diff --git a/src/coreclr/jit/target.h b/src/coreclr/jit/target.h index 633f5dc34d22b..d4d501e5fd72d 100644 --- a/src/coreclr/jit/target.h +++ b/src/coreclr/jit/target.h @@ -436,7 +436,7 @@ typedef unsigned char regNumberSmall; #define FIRST_ARG_STACK_OFFS (2*REGSIZE_BYTES) // Caller's saved EBP and return address #define MAX_REG_ARG 2 - + #define MAX_FLOAT_REG_ARG 0 #define REG_ARG_FIRST REG_ECX #define REG_ARG_LAST REG_EDX @@ -1620,6 +1620,7 @@ class Target ARG_ORDER_L2R }; static const enum ArgOrder g_tgtArgOrder; + static const enum ArgOrder g_tgtUnmanagedArgOrder; }; #if defined(DEBUG) || defined(LATE_DISASM) || DUMP_GC_TABLES diff --git a/src/coreclr/jit/targetamd64.cpp b/src/coreclr/jit/targetamd64.cpp index 372c4dffc27b2..4ac48cb229fbe 100644 --- a/src/coreclr/jit/targetamd64.cpp +++ b/src/coreclr/jit/targetamd64.cpp @@ -12,8 +12,9 @@ #include "target.h" -const char* Target::g_tgtCPUName = "x64"; -const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const char* Target::g_tgtCPUName = "x64"; +const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const Target::ArgOrder Target::g_tgtUnmanagedArgOrder = ARG_ORDER_R2L; // clang-format off #ifdef UNIX_AMD64_ABI diff --git a/src/coreclr/jit/targetarm.cpp b/src/coreclr/jit/targetarm.cpp index da125cbb436a0..dbb986a0e05b0 100644 --- a/src/coreclr/jit/targetarm.cpp +++ b/src/coreclr/jit/targetarm.cpp @@ -12,8 +12,9 @@ #include "target.h" -const char* Target::g_tgtCPUName = "arm"; -const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const char* Target::g_tgtCPUName = "arm"; +const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const Target::ArgOrder Target::g_tgtUnmanagedArgOrder = ARG_ORDER_R2L; // clang-format off const regNumber intArgRegs [] = {REG_R0, REG_R1, REG_R2, REG_R3}; diff --git a/src/coreclr/jit/targetarm64.cpp b/src/coreclr/jit/targetarm64.cpp index 8f5481a83e02d..dcec1db6c5229 100644 --- a/src/coreclr/jit/targetarm64.cpp +++ b/src/coreclr/jit/targetarm64.cpp @@ -12,8 +12,9 @@ #include "target.h" -const char* Target::g_tgtCPUName = "arm64"; -const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const char* Target::g_tgtCPUName = "arm64"; +const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_R2L; +const Target::ArgOrder Target::g_tgtUnmanagedArgOrder = ARG_ORDER_R2L; // clang-format off const regNumber intArgRegs [] = {REG_R0, REG_R1, REG_R2, REG_R3, REG_R4, REG_R5, REG_R6, REG_R7}; diff --git a/src/coreclr/jit/targetx86.cpp b/src/coreclr/jit/targetx86.cpp index fab7286782a2d..d5ed8b0bbf606 100644 --- a/src/coreclr/jit/targetx86.cpp +++ b/src/coreclr/jit/targetx86.cpp @@ -12,8 +12,9 @@ #include "target.h" -const char* Target::g_tgtCPUName = "x86"; -const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_L2R; +const char* Target::g_tgtCPUName = "x86"; +const Target::ArgOrder Target::g_tgtArgOrder = ARG_ORDER_L2R; +const Target::ArgOrder Target::g_tgtUnmanagedArgOrder = ARG_ORDER_R2L; // clang-format off const regNumber intArgRegs [] = {REG_ECX, REG_EDX}; diff --git a/src/coreclr/pal/src/config.h.in b/src/coreclr/pal/src/config.h.in index 7b097c99ced43..10f281b02bfcd 100644 --- a/src/coreclr/pal/src/config.h.in +++ b/src/coreclr/pal/src/config.h.in @@ -17,7 +17,6 @@ #cmakedefine01 HAVE_SYS_LWP_H #cmakedefine01 HAVE_LWP_H #cmakedefine01 HAVE_RUNETYPE_H -#cmakedefine01 HAVE_SYS_SYSCTL_H #cmakedefine01 HAVE_GNU_LIBNAMES_H #cmakedefine01 HAVE_PRCTL_H #cmakedefine01 HAVE_NUMA_H diff --git a/src/coreclr/pal/src/configure.cmake b/src/coreclr/pal/src/configure.cmake index b0ea1524f2b0d..af9836c894db9 100644 --- a/src/coreclr/pal/src/configure.cmake +++ b/src/coreclr/pal/src/configure.cmake @@ -77,7 +77,6 @@ int main(int argc, char **argv) { set(CMAKE_REQUIRED_LIBRARIES) -check_include_files(sys/sysctl.h HAVE_SYS_SYSCTL_H) check_function_exists(sysctlbyname HAVE_SYSCTLBYNAME) check_include_files(gnu/lib-names.h HAVE_GNU_LIBNAMES_H) @@ -113,7 +112,12 @@ set(CMAKE_REQUIRED_LIBRARIES) check_function_exists(fsync HAVE_FSYNC) check_function_exists(futimes HAVE_FUTIMES) check_function_exists(utimes HAVE_UTIMES) -check_function_exists(sysctl HAVE_SYSCTL) +if(CLR_CMAKE_TARGET_LINUX) + # sysctl is deprecated on Linux + set(HAVE_SYSCTL 0) +else() + check_function_exists(sysctl HAVE_SYSCTL) +endif() check_function_exists(sysinfo HAVE_SYSINFO) check_function_exists(sysconf HAVE_SYSCONF) check_function_exists(gmtime_r HAVE_GMTIME_R) diff --git a/src/coreclr/runtime.proj b/src/coreclr/runtime.proj index 28f3555eafeae..800ab9743ba48 100644 --- a/src/coreclr/runtime.proj +++ b/src/coreclr/runtime.proj @@ -8,7 +8,8 @@ AfterTargets="Build"> <_CoreClrBuildArg Condition="'$(TargetArchitecture)' != ''" Include="-$(TargetArchitecture)" /> - <_CoreClrBuildArg Include="$(CMakeArgs)" /> + <_CoreClrBuildArg Condition="!$([MSBuild]::IsOsPlatform(Windows)) and '$(CMakeArgs)' != ''" Include="$(CMakeArgs)" /> + <_CoreClrBuildArg Condition="$([MSBuild]::IsOsPlatform(Windows)) and '$(CMakeArgs)' != ''" Include="-cmakeargs "$(CMakeArgs)"" /> <_CoreClrBuildArg Include="-$(Configuration.ToLower())" /> <_CoreClrBuildArg Include="$(Compiler)" /> <_CoreClrBuildArg Condition="'$(ContinuousIntegrationBuild)' == 'true'" Include="-ci" /> diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithCodeInfo.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithCodeInfo.cs index 9f6d217490600..62e8c282fc334 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithCodeInfo.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/INodeWithCodeInfo.cs @@ -47,29 +47,4 @@ public DebugEHClauseInfo(uint tryOffset, uint tryLength, uint handlerOffset, uin HandlerLength = handlerLength; } } - - public interface INodeWithCodeInfo - { - FrameInfo[] FrameInfos - { - get; - } - - byte[] GCInfo - { - get; - } - - DebugEHClauseInfo[] DebugEHClauseInfos - { - get; - } - - ObjectNode.ObjectData EHInfo - { - get; - } - - ISymbolNode GetAssociatedDataNode(NodeFactory factory); - } } diff --git a/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs b/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs new file mode 100644 index 0000000000000..674d54a95e059 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/DisplayNameHelpers.cs @@ -0,0 +1,206 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace ILCompiler +{ + internal static class DisplayNameHelpers + { + public static string GetDisplayName(this MethodDesc method) + { + var sb = new StringBuilder(); + + sb.Append(method.OwningType.GetDisplayName()); + sb.Append('.'); + + if (method.IsConstructor) + { + sb.Append(method.OwningType.GetDisplayNameWithoutNamespace()); + } + else + { + sb.Append(method.Name); + } + + if (method.HasInstantiation) + { + sb.Append('<'); + for (int i = 0; i < method.Instantiation.Length - 1; i++) + sb.Append(method.Instantiation[i].GetDisplayNameWithoutNamespace()).Append(','); + + sb.Append(method.Instantiation[method.Instantiation.Length - 1].GetDisplayNameWithoutNamespace()); + sb.Append('>'); + } + + // Append parameters + sb.Append('('); + if (method.Signature.Length > 0) + { + for (int i = 0; i < method.Signature.Length - 1; i++) + sb.Append(method.Signature[i].GetDisplayNameWithoutNamespace()).Append(','); + + sb.Append(method.Signature[method.Signature.Length - 1].GetDisplayNameWithoutNamespace()); + } + + sb.Append(')'); + + return sb.ToString(); + } + + public static string GetDisplayName(this FieldDesc field) + { + return new StringBuilder(field.OwningType.GetDisplayName()) + .Append('.') + .Append(field.Name).ToString(); + } + +#if !READYTORUN + public static string GetDisplayName(this PropertyPseudoDesc property) + { + return new StringBuilder(property.OwningType.GetDisplayName()) + .Append('.') + .Append(property.Name).ToString(); + } +#endif + + public static string GetDisplayName(this TypeDesc type) + { + return Formatter.Instance.FormatName(type, FormatOptions.NamespaceQualify); + } + + public static string GetDisplayNameWithoutNamespace(this TypeDesc type) + { + return Formatter.Instance.FormatName(type, FormatOptions.None); + } + + private class Formatter : TypeNameFormatter + { + public readonly static Formatter Instance = new Formatter(); + + public override Unit AppendName(StringBuilder sb, ArrayType type, FormatOptions options) + { + AppendName(sb, type.ElementType, options); + sb.Append('['); + if (type.Rank > 1) + sb.Append(new string(',', type.Rank - 1)); + sb.Append(']'); + return default; + } + + public override Unit AppendName(StringBuilder sb, ByRefType type, FormatOptions options) + { + AppendName(sb, type.ParameterType, options); + sb.Append('&'); + return default; + } + + public override Unit AppendName(StringBuilder sb, PointerType type, FormatOptions options) + { + AppendName(sb, type.ParameterType, options); + sb.Append('*'); + return default; + } + + public override Unit AppendName(StringBuilder sb, FunctionPointerType type, FormatOptions options) + { + MethodSignature signature = type.Signature; + + sb.Append("delegate*<"); + for (int i = 0; i < signature.Length; i++) + { + AppendName(sb, signature[i], options); + sb.Append(','); + } + AppendName(sb, signature.ReturnType, options); + sb.Append('>'); + + return default; + } + + public override Unit AppendName(StringBuilder sb, GenericParameterDesc type, FormatOptions options) + { + sb.Append(type.Name); + return default; + } + + public override Unit AppendName(StringBuilder sb, SignatureMethodVariable type, FormatOptions options) + { + sb.Append("!!" + type.Index); + return default; + } + + public override Unit AppendName(StringBuilder sb, SignatureTypeVariable type, FormatOptions options) + { + sb.Append("!" + type.Index); + return default; + } + + protected override Unit AppendNameForInstantiatedType(StringBuilder sb, DefType type, FormatOptions options) + { + AppendName(sb, type.GetTypeDefinition(), options); + + FormatOptions parameterOptions = options & ~FormatOptions.NamespaceQualify; + + sb.Append('<'); + + for (int i = 0; i < type.Instantiation.Length; i++) + { + if (i != 0) + sb.Append(','); + + AppendName(sb, type.Instantiation[i], parameterOptions); + } + + sb.Append('>'); + + return default; + } + + protected override Unit AppendNameForNamespaceType(StringBuilder sb, DefType type, FormatOptions options) + { + NamespaceQualify(sb, type, options); + sb.Append(type.Name); + return default; + } + + protected override Unit AppendNameForNestedType(StringBuilder sb, DefType nestedType, DefType containingType, FormatOptions options) + { + if ((options & FormatOptions.NamespaceQualify) != 0) + { + AppendName(sb, containingType, options); + sb.Append('.'); + } + + sb.Append(nestedType.Name); + + return default; + } + + private void NamespaceQualify(StringBuilder sb, DefType type, FormatOptions options) + { + if ((options & FormatOptions.NamespaceQualify) != 0) + { + string ns = type.Namespace; + if (!string.IsNullOrEmpty(ns)) + { + sb.Append(ns); + sb.Append('.'); + } + } + } + + public struct Unit { } + } + + private enum FormatOptions + { + None = 0, + NamespaceQualify = 1, + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/Logger.cs b/src/coreclr/tools/Common/Compiler/Logger.cs index af2d728577c0c..55f87b204db77 100644 --- a/src/coreclr/tools/Common/Compiler/Logger.cs +++ b/src/coreclr/tools/Common/Compiler/Logger.cs @@ -1,24 +1,130 @@ // 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.Reflection.Metadata; using System.IO; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +using ILCompiler.Logging; + +using ILSequencePoint = Internal.IL.ILSequencePoint; +using MethodIL = Internal.IL.MethodIL; + namespace ILCompiler { - // Poor man's logger. We can do better than this. - public class Logger { + private readonly HashSet _suppressedWarnings; + public static Logger Null = new Logger(TextWriter.Null, false); public TextWriter Writer { get; } public bool IsVerbose { get; } - public Logger(TextWriter writer, bool isVerbose) + public Logger(TextWriter writer, bool isVerbose, IEnumerable suppressedWarnings) { Writer = TextWriter.Synchronized(writer); IsVerbose = isVerbose; + _suppressedWarnings = new HashSet(suppressedWarnings); + } + + public Logger(TextWriter writer, bool isVerbose) + : this(writer, isVerbose, Array.Empty()) + { + } + + public void LogWarning(string text, int code, MessageOrigin origin, string subcategory = MessageSubCategory.None) + { + MessageContainer? warning = MessageContainer.CreateWarningMessage(this, text, code, origin, subcategory); + if (warning.HasValue) + Writer.WriteLine(warning.Value.ToMSBuildString()); + } + + public void LogWarning(string text, int code, TypeSystemEntity origin, string subcategory = MessageSubCategory.None) + { + MessageOrigin messageOrigin = new MessageOrigin(origin); + MessageContainer? warning = MessageContainer.CreateWarningMessage(this, text, code, messageOrigin, subcategory); + if (warning.HasValue) + Writer.WriteLine(warning.Value.ToMSBuildString()); + } + + public void LogWarning(string text, int code, MethodIL origin, int ilOffset, string subcategory = MessageSubCategory.None) + { + string document = null; + int? lineNumber = null; + + IEnumerable sequencePoints = origin.GetDebugInfo()?.GetSequencePoints(); + if (sequencePoints != null) + { + foreach (var sequencePoint in sequencePoints) + { + if (sequencePoint.Offset <= ilOffset) + { + document = sequencePoint.Document; + lineNumber = sequencePoint.LineNumber; + } + } + } + + MessageOrigin messageOrigin = new MessageOrigin(origin.OwningMethod, document, lineNumber, null); + LogWarning(text, code, messageOrigin, subcategory); + } + + internal bool IsWarningSuppressed(int code, MessageOrigin origin) + { + if (_suppressedWarnings.Contains(code)) + return true; + + IEnumerable> suppressions = null; + + // TODO: Suppressions with different scopes + + + if (origin.MemberDefinition is MethodDesc method) + { + var ecmaMethod = method.GetTypicalMethodDefinition() as EcmaMethod; + suppressions = ecmaMethod?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute"); + } + + if (suppressions != null) + { + foreach (CustomAttributeValue suppression in suppressions) + { + if (suppression.FixedArguments.Length != 2 + || suppression.FixedArguments[1].Value is not string warningId + || warningId.Length < 6 + || !warningId.StartsWith("IL") + || (warningId.Length > 6 && warningId[6] != ':') + || !int.TryParse(warningId.Substring(2, 4), out int suppressedCode)) + { + continue; + } + + if (code == suppressedCode) + { + return true; + } + } + } + + return false; } + + internal bool IsWarningAsError(int code) + { + // TODO: warnaserror + return false; + } + } + + public static class MessageSubCategory + { + public const string None = ""; + public const string TrimAnalysis = "Trim analysis"; } } diff --git a/src/coreclr/tools/Common/Compiler/Logging/MessageContainer.cs b/src/coreclr/tools/Common/Compiler/Logging/MessageContainer.cs new file mode 100644 index 0000000000000..7f1da04ba3271 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/Logging/MessageContainer.cs @@ -0,0 +1,206 @@ +// 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.Text; +using Internal.TypeSystem; + +namespace ILCompiler.Logging +{ + public enum MessageCategory + { + Error = 0, + Warning, + Info, + Diagnostic, + + WarningAsError = 0xFF + } + + public readonly struct MessageContainer +#if false + : IComparable, IEquatable +#endif + { + /// + /// Optional data with a filename, line and column that triggered the + /// linker to output an error (or warning) message. + /// + public MessageOrigin? Origin { get; } + + public MessageCategory Category { get; } + + /// + /// Further categorize the message. + /// + public string SubCategory { get; } + + /// + /// Code identifier for errors and warnings reported by the IL linker. + /// + public int? Code { get; } + + /// + /// User friendly text describing the error or warning. + /// + public string Text { get; } + + /// + /// Create an error message. + /// + /// Humanly readable message describing the error + /// Unique error ID. Please see https://github.com/mono/linker/blob/master/doc/error-codes.md + /// for the list of errors and possibly add a new one + /// Optionally, further categorize this error + /// Filename, line, and column where the error was found + /// New MessageContainer of 'Error' category + internal static MessageContainer CreateErrorMessage(string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null) + { + if (!(code >= 1000 && code <= 2000)) + throw new ArgumentOutOfRangeException(nameof(code), $"The provided code '{code}' does not fall into the error category, which is in the range of 1000 to 2000 (inclusive)."); + + return new MessageContainer(MessageCategory.Error, text, code, subcategory, origin); + } + + /// + /// Create a warning message. + /// + /// Context with the relevant warning suppression info. + /// Humanly readable message describing the warning + /// Unique warning ID. Please see https://github.com/mono/linker/blob/master/doc/error-codes.md + /// for the list of warnings and possibly add a new one + /// /// Filename or member where the warning is coming from + /// Optionally, further categorize this warning + /// Optional warning version number. Versioned warnings can be controlled with the + /// warning wave option --warn VERSION. Unversioned warnings are unaffected by this option. + /// New MessageContainer of 'Warning' category + internal static MessageContainer? CreateWarningMessage(Logger context, string text, int code, MessageOrigin origin, string subcategory = MessageSubCategory.None) + { + if (!(code > 2000 && code <= 6000)) + throw new ArgumentOutOfRangeException(nameof(code), $"The provided code '{code}' does not fall into the warning category, which is in the range of 2001 to 6000 (inclusive)."); + + return CreateWarningMessageContainer(context, text, code, origin, subcategory); + } + + private static MessageContainer? CreateWarningMessageContainer(Logger context, string text, int code, MessageOrigin origin, string subcategory = MessageSubCategory.None) + { + if (context.IsWarningSuppressed(code, origin)) + return null; + + if (context.IsWarningAsError(code)) + return new MessageContainer(MessageCategory.WarningAsError, text, code, subcategory, origin); + + return new MessageContainer(MessageCategory.Warning, text, code, subcategory, origin); + } + + /// + /// Create a info message. + /// + /// Humanly readable message + /// New MessageContainer of 'Info' category + public static MessageContainer CreateInfoMessage(string text) + { + return new MessageContainer(MessageCategory.Info, text, null); + } + + /// + /// Create a diagnostics message. + /// + /// Humanly readable message + /// New MessageContainer of 'Diagnostic' category + public static MessageContainer CreateDiagnosticMessage(string text) + { + return new MessageContainer(MessageCategory.Diagnostic, text, null); + } + + private MessageContainer(MessageCategory category, string text, int? code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null) + { + Code = code; + Category = category; + Origin = origin; + SubCategory = subcategory; + Text = text; + } + + public override string ToString() => ToMSBuildString(); + + public string ToMSBuildString() + { + const string originApp = "ILC"; + string origin = Origin?.ToString() ?? originApp; + + StringBuilder sb = new StringBuilder(); + sb.Append(origin).Append(":"); + + if (!string.IsNullOrEmpty(SubCategory)) + sb.Append(" ").Append(SubCategory); + + string cat; + switch (Category) + { + case MessageCategory.Error: + case MessageCategory.WarningAsError: + cat = "error"; + break; + case MessageCategory.Warning: + cat = "warning"; + break; + default: + cat = ""; + break; + } + + if (!string.IsNullOrEmpty(cat)) + { + sb.Append(" ") + .Append(cat) + .Append(" IL") + .Append(Code.Value.ToString("D4")) + .Append(": "); + } + else + { + sb.Append(" "); + } + + if (Origin?.MemberDefinition != null) + { + if (Origin?.MemberDefinition is MethodDesc method) + sb.Append(method.GetDisplayName()); + else + sb.Append(Origin?.MemberDefinition.ToString()); + + sb.Append(": "); + } + + // Expected output $"{FileName(SourceLine, SourceColumn)}: {SubCategory}{Category} IL{Code}: ({MemberDisplayName}: ){Text}"); + sb.Append(Text); + return sb.ToString(); + } + +#if false + public bool Equals(MessageContainer other) => + (Category, Text, Code, SubCategory, Origin) == (other.Category, other.Text, other.Code, other.SubCategory, other.Origin); + + public override bool Equals(object obj) => obj is MessageContainer messageContainer && Equals(messageContainer); + public override int GetHashCode() => (Category, Text, Code, SubCategory, Origin).GetHashCode(); + + public int CompareTo(MessageContainer other) + { + if (Origin != null && other.Origin != null) + { + return Origin.Value.CompareTo(other.Origin.Value); + } + else if (Origin == null && other.Origin == null) + { + return (Code < other.Code) ? -1 : 1; + } + + return (Origin == null) ? 1 : -1; + } + + public static bool operator ==(MessageContainer lhs, MessageContainer rhs) => lhs.Equals(rhs); + public static bool operator !=(MessageContainer lhs, MessageContainer rhs) => !lhs.Equals(rhs); +#endif + } +} diff --git a/src/coreclr/tools/Common/Compiler/Logging/MessageOrigin.cs b/src/coreclr/tools/Common/Compiler/Logging/MessageOrigin.cs new file mode 100644 index 0000000000000..5e162e711d5ad --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/Logging/MessageOrigin.cs @@ -0,0 +1,90 @@ +// 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.Text; + +using Internal.TypeSystem; + +namespace ILCompiler.Logging +{ + public struct MessageOrigin +#if false + : IComparable, IEquatable +#endif + { + public string FileName { get; } + public TypeSystemEntity MemberDefinition { get; } + + public int? SourceLine { get; } + public int? SourceColumn { get; } + + public MessageOrigin(string fileName, int sourceLine = 0, int sourceColumn = 0) + { + FileName = fileName; + SourceLine = sourceLine; + SourceColumn = sourceColumn; + MemberDefinition = null; + } + + public MessageOrigin(TypeSystemEntity memberDefinition, string fileName = null, int? sourceLine = 0, int? sourceColumn = 0) + { + FileName = fileName; + MemberDefinition = memberDefinition; + SourceLine = sourceLine; + SourceColumn = sourceColumn; + } + + public override string ToString() + { + if (FileName == null) + return null; + + StringBuilder sb = new StringBuilder(FileName); + if (SourceLine.HasValue) + { + sb.Append("(").Append(SourceLine); + if (SourceColumn.HasValue) + sb.Append(",").Append(SourceColumn); + + sb.Append(")"); + } + + return sb.ToString(); + } + +#if false + public bool Equals(MessageOrigin other) => + (FileName, MemberDefinition, SourceLine, SourceColumn) == (other.FileName, other.MemberDefinition, other.SourceLine, other.SourceColumn); + + public override bool Equals(object obj) => obj is MessageOrigin messageOrigin && Equals(messageOrigin); + public override int GetHashCode() => (FileName, MemberDefinition, SourceLine, SourceColumn).GetHashCode(); + public static bool operator ==(MessageOrigin lhs, MessageOrigin rhs) => lhs.Equals(rhs); + public static bool operator !=(MessageOrigin lhs, MessageOrigin rhs) => !lhs.Equals(rhs); + + public int CompareTo(MessageOrigin other) + { + if (MemberDefinition != null && other.MemberDefinition != null) + { + return (MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, MemberDefinition.DeclaringType?.Name, MemberDefinition?.Name).CompareTo + ((other.MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, other.MemberDefinition.DeclaringType?.Name, other.MemberDefinition?.Name)); + } + else if (MemberDefinition == null && other.MemberDefinition == null) + { + if (FileName != null && other.FileName != null) + { + return string.Compare(FileName, other.FileName); + } + else if (FileName == null && other.FileName == null) + { + return (SourceLine, SourceColumn).CompareTo((other.SourceLine, other.SourceColumn)); + } + + return (FileName == null) ? 1 : -1; + } + + return (MemberDefinition == null) ? 1 : -1; + } +#endif + } +} diff --git a/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageContainer.cs b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageContainer.cs new file mode 100644 index 0000000000000..7a6fa377d16fd --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageContainer.cs @@ -0,0 +1,241 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using Mono.Cecil; + +namespace Mono.Linker +{ + public readonly struct MessageContainer : IComparable, IEquatable + { + public static readonly MessageContainer Empty; + + /// + /// Optional data with a filename, line and column that triggered the + /// linker to output an error (or warning) message. + /// + public MessageOrigin? Origin { get; } + + public MessageCategory Category { get; } + + /// + /// Further categorize the message. + /// + public string SubCategory { get; } + + /// + /// Code identifier for errors and warnings reported by the IL linker. + /// + public int? Code { get; } + + /// + /// User friendly text describing the error or warning. + /// + public string Text { get; } + + /// + /// Create an error message. + /// + /// Humanly readable message describing the error + /// Unique error ID. Please see https://github.com/mono/linker/blob/master/doc/error-codes.md + /// for the list of errors and possibly add a new one + /// Optionally, further categorize this error + /// Filename, line, and column where the error was found + /// New MessageContainer of 'Error' category + internal static MessageContainer CreateErrorMessage (string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null) + { + if (!(code >= 1000 && code <= 2000)) + throw new ArgumentOutOfRangeException (nameof (code), $"The provided code '{code}' does not fall into the error category, which is in the range of 1000 to 2000 (inclusive)."); + + return new MessageContainer (MessageCategory.Error, text, code, subcategory, origin); + } + + /// + /// Create a custom error message. + /// + /// Humanly readable message describing the error + /// A custom error ID. This code should be greater than or equal to 6001 + /// to avoid any collisions with existing and future linker errors + /// Optionally, further categorize this error + /// Filename or member where the error is coming from + /// Custom MessageContainer of 'Error' category + public static MessageContainer CreateCustomErrorMessage (string text, int code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null) + { +#if DEBUG + Debug.Assert (Assembly.GetCallingAssembly () != typeof (MessageContainer).Assembly, + "'CreateCustomErrorMessage' is intended to be used by external assemblies only. Use 'CreateErrorMessage' instead."); +#endif + if (code <= 6000) + throw new ArgumentOutOfRangeException (nameof (code), $"The provided code '{code}' does not fall into the permitted range for external errors. To avoid possible collisions " + + "with existing and future {Constants.ILLink} errors, external messages should use codes starting from 6001."); + + return new MessageContainer (MessageCategory.Error, text, code, subcategory, origin); + } + + /// + /// Create a warning message. + /// + /// Context with the relevant warning suppression info. + /// Humanly readable message describing the warning + /// Unique warning ID. Please see https://github.com/mono/linker/blob/master/doc/error-codes.md + /// for the list of warnings and possibly add a new one + /// /// Filename or member where the warning is coming from + /// Optionally, further categorize this warning + /// Optional warning version number. Versioned warnings can be controlled with the + /// warning wave option --warn VERSION. Unversioned warnings are unaffected by this option. + /// New MessageContainer of 'Warning' category + internal static MessageContainer CreateWarningMessage (LinkContext context, string text, int code, MessageOrigin origin, WarnVersion version, string subcategory = MessageSubCategory.None) + { + if (!(code > 2000 && code <= 6000)) + throw new ArgumentOutOfRangeException (nameof (code), $"The provided code '{code}' does not fall into the warning category, which is in the range of 2001 to 6000 (inclusive)."); + + return CreateWarningMessageContainer (context, text, code, origin, version, subcategory); + } + + /// + /// Create a custom warning message. + /// + /// Context with the relevant warning suppression info. + /// Humanly readable message describing the warning + /// A custom warning ID. This code should be greater than or equal to 6001 + /// to avoid any collisions with existing and future linker warnings + /// Filename or member where the warning is coming from + /// Optional warning version number. Versioned warnings can be controlled with the + /// warning wave option --warn VERSION. Unversioned warnings are unaffected by this option + /// + /// Custom MessageContainer of 'Warning' category + public static MessageContainer CreateCustomWarningMessage (LinkContext context, string text, int code, MessageOrigin origin, WarnVersion version, string subcategory = MessageSubCategory.None) + { +#if DEBUG + Debug.Assert (Assembly.GetCallingAssembly () != typeof (MessageContainer).Assembly, + "'CreateCustomWarningMessage' is intended to be used by external assemblies only. Use 'CreateWarningMessage' instead."); +#endif + if (code <= 6000) + throw new ArgumentOutOfRangeException (nameof (code), $"The provided code '{code}' does not fall into the permitted range for external warnings. To avoid possible collisions " + + $"with existing and future {Constants.ILLink} warnings, external messages should use codes starting from 6001."); + + return CreateWarningMessageContainer (context, text, code, origin, version, subcategory); + } + + private static MessageContainer CreateWarningMessageContainer (LinkContext context, string text, int code, MessageOrigin origin, WarnVersion version, string subcategory = MessageSubCategory.None) + { + if (!(version >= WarnVersion.ILLink0 && version <= WarnVersion.Latest)) + throw new ArgumentException ($"The provided warning version '{version}' is invalid."); + + if (context.IsWarningSuppressed (code, origin)) + return Empty; + + if (version > context.WarnVersion) + return Empty; + + if (context.IsWarningAsError (code)) + return new MessageContainer (MessageCategory.WarningAsError, text, code, subcategory, origin); + + return new MessageContainer (MessageCategory.Warning, text, code, subcategory, origin); + } + + /// + /// Create a info message. + /// + /// Humanly readable message + /// New MessageContainer of 'Info' category + public static MessageContainer CreateInfoMessage (string text) + { + return new MessageContainer (MessageCategory.Info, text, null); + } + + /// + /// Create a diagnostics message. + /// + /// Humanly readable message + /// New MessageContainer of 'Diagnostic' category + public static MessageContainer CreateDiagnosticMessage (string text) + { + return new MessageContainer (MessageCategory.Diagnostic, text, null); + } + + private MessageContainer (MessageCategory category, string text, int? code, string subcategory = MessageSubCategory.None, MessageOrigin? origin = null) + { + Code = code; + Category = category; + Origin = origin; + SubCategory = subcategory; + Text = text; + } + + public override string ToString () => ToMSBuildString (); + + public string ToMSBuildString () + { + const string originApp = Constants.ILLink; + string origin = Origin?.ToString () ?? originApp; + + StringBuilder sb = new StringBuilder (); + sb.Append (origin).Append (":"); + + if (!string.IsNullOrEmpty (SubCategory)) + sb.Append (" ").Append (SubCategory); + + string cat; + switch (Category) { + case MessageCategory.Error: + case MessageCategory.WarningAsError: + cat = "error"; + break; + case MessageCategory.Warning: + cat = "warning"; + break; + default: + cat = ""; + break; + } + + if (!string.IsNullOrEmpty (cat)) { + sb.Append (" ") + .Append (cat) + .Append (" IL") + .Append (Code.Value.ToString ("D4")) + .Append (": "); + } else { + sb.Append (" "); + } + + if (Origin?.MemberDefinition != null) { + if (Origin?.MemberDefinition is MethodDefinition method) + sb.Append (method.GetDisplayName ()); + else + sb.Append (Origin?.MemberDefinition.FullName); + + sb.Append (": "); + } + + // Expected output $"{FileName(SourceLine, SourceColumn)}: {SubCategory}{Category} IL{Code}: ({MemberDisplayName}: ){Text}"); + sb.Append (Text); + return sb.ToString (); + } + + public bool Equals (MessageContainer other) => + (Category, Text, Code, SubCategory, Origin) == (other.Category, other.Text, other.Code, other.SubCategory, other.Origin); + + public override bool Equals (object obj) => obj is MessageContainer messageContainer && Equals (messageContainer); + public override int GetHashCode () => (Category, Text, Code, SubCategory, Origin).GetHashCode (); + + public int CompareTo (MessageContainer other) + { + if (Origin != null && other.Origin != null) { + return Origin.Value.CompareTo (other.Origin.Value); + } else if (Origin == null && other.Origin == null) { + return (Code < other.Code) ? -1 : 1; + } + + return (Origin == null) ? 1 : -1; + } + + public static bool operator == (MessageContainer lhs, MessageContainer rhs) => lhs.Equals (rhs); + public static bool operator != (MessageContainer lhs, MessageContainer rhs) => !lhs.Equals (rhs); + } +} diff --git a/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageOrigin.cs b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageOrigin.cs new file mode 100644 index 0000000000000..62725b69044a2 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/MessageOrigin.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Linq; +using System.Text; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Mono.Linker +{ + public readonly struct MessageOrigin : IComparable, IEquatable + { +#nullable enable + public string? FileName { get; } + public IMemberDefinition? MemberDefinition { get; } +#nullable disable + public int SourceLine { get; } + public int SourceColumn { get; } + public int? ILOffset { get; } + + public MessageOrigin (string fileName, int sourceLine = 0, int sourceColumn = 0) + { + FileName = fileName; + SourceLine = sourceLine; + SourceColumn = sourceColumn; + MemberDefinition = null; + ILOffset = null; + } + + public MessageOrigin (IMemberDefinition memberDefinition, int? ilOffset = null) + { + FileName = null; + MemberDefinition = memberDefinition; + SourceLine = 0; + SourceColumn = 0; + ILOffset = ilOffset; + } + + public override string ToString () + { + int sourceLine = SourceLine, sourceColumn = SourceColumn; + string fileName = FileName; + if (MemberDefinition is MethodDefinition method && + method.DebugInformation.HasSequencePoints) { + var offset = ILOffset ?? 0; + SequencePoint correspondingSequencePoint = method.DebugInformation.SequencePoints + .Where (s => s.Offset <= offset)?.Last (); + if (correspondingSequencePoint != null) { + fileName = correspondingSequencePoint.Document.Url; + sourceLine = correspondingSequencePoint.StartLine; + sourceColumn = correspondingSequencePoint.StartColumn; + } + } + + if (fileName == null) + return null; + + StringBuilder sb = new StringBuilder (fileName); + if (sourceLine != 0) { + sb.Append ("(").Append (sourceLine); + if (sourceColumn != 0) + sb.Append (",").Append (sourceColumn); + + sb.Append (")"); + } + + return sb.ToString (); + } + + public bool Equals (MessageOrigin other) => + (FileName, MemberDefinition, SourceLine, SourceColumn) == (other.FileName, other.MemberDefinition, other.SourceLine, other.SourceColumn); + + public override bool Equals (object obj) => obj is MessageOrigin messageOrigin && Equals (messageOrigin); + public override int GetHashCode () => (FileName, MemberDefinition, SourceLine, SourceColumn).GetHashCode (); + public static bool operator == (MessageOrigin lhs, MessageOrigin rhs) => lhs.Equals (rhs); + public static bool operator != (MessageOrigin lhs, MessageOrigin rhs) => !lhs.Equals (rhs); + + public int CompareTo (MessageOrigin other) + { + if (MemberDefinition != null && other.MemberDefinition != null) { + return (MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, MemberDefinition.DeclaringType?.Name, MemberDefinition?.Name).CompareTo + ((other.MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, other.MemberDefinition.DeclaringType?.Name, other.MemberDefinition?.Name)); + } else if (MemberDefinition == null && other.MemberDefinition == null) { + if (FileName != null && other.FileName != null) { + return string.Compare (FileName, other.FileName); + } else if (FileName == null && other.FileName == null) { + return (SourceLine, SourceColumn).CompareTo ((other.SourceLine, other.SourceColumn)); + } + + return (FileName == null) ? 1 : -1; + } + + return (MemberDefinition == null) ? 1 : -1; + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/README.md b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/README.md new file mode 100644 index 0000000000000..90170112b5d4a --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/Logging/ReferenceSource/README.md @@ -0,0 +1 @@ +Sources from the mono/linker repo at commit 8ee2557ccbaf9e4cf243f15b8cb95da4eddb18aa. \ No newline at end of file diff --git a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs index d579a655d2829..d2fd2fd05b764 100644 --- a/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs +++ b/src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Internal.TypeSystem; namespace Internal.ReadyToRunConstants { @@ -115,7 +116,7 @@ public enum ReadyToRunFixupKind IndirectPInvokeTarget = 0x2E, // Target (indirect) of an inlined pinvoke PInvokeTarget = 0x2F, // Target of an inlined pinvoke - Check_InstructionSetSupport = 0x30, // Define the set of instruction sets that must be supported/unsupported to use the fixup + Check_InstructionSetSupport = 0x30, // Define the set of instruction sets that must be supported/unsupported to use the fixup Verify_FieldOffset = 0x31, // Generate a runtime check to ensure that the field offset matches between compile and runtime. Unlike CheckFieldOffset, this will generate a runtime exception on failure instead of silently dropping the method Verify_TypeLayout = 0x32, // Generate a runtime check to ensure that the type layout (size, alignment, HFA, reference map) matches between compile and runtime. Unlike Check_TypeLayout, this will generate a runtime failure instead of silently dropping the method @@ -330,6 +331,6 @@ public enum ReadyToRunHFAElemType public static class ReadyToRunRuntimeConstants { public const int READYTORUN_PInvokeTransitionFrameSizeInPointerUnits = 11; - public const int READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits = 2; + public static int READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits(TargetArchitecture target) => target == TargetArchitecture.X86 ? 5 : 2; } } diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs index 8f22b0c48b85f..6bc8a1999877a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs @@ -288,8 +288,10 @@ which is the right helper to use to allocate an object of a given type. */ CORINFO_HELP_JIT_PINVOKE_BEGIN, // Transition to preemptive mode before a P/Invoke, frame is the first argument CORINFO_HELP_JIT_PINVOKE_END, // Transition to cooperative mode after a P/Invoke, frame is the first argument - CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER, // Transition to cooperative mode in reverse P/Invoke prolog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_ENTER_TRACK_TRANSITIONS, // Transition to cooperative mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT, // Transition to preemptive mode in reverse P/Invoke epilog, frame is the first argument + CORINFO_HELP_JIT_REVERSE_PINVOKE_EXIT_TRACK_TRANSITIONS, // Transition to preemptive mode and track transitions in reverse P/Invoke prolog. CORINFO_HELP_GVMLOOKUP_FOR_SLOT, // Resolve a generic virtual method target from this pointer and runtime method handle diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 486ef28bc093e..e87fc0fef55b5 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -297,7 +297,12 @@ private void PublishCode() var node = _compilation.SymbolNodeFactory.PerMethodInstructionSetSupportFixup(actualSupport); _methodCodeNode.Fixups.Add(node); } +#else + MethodIL methodIL = (MethodIL)HandleToObject((IntPtr)_methodScope); + CodeBasedDependencyAlgorithm.AddDependenciesDueToMethodCodePresence(ref _additionalDependencies, _compilation.NodeFactory, MethodBeingCompiled, methodIL); + _methodCodeNode.InitializeNonRelocationDependencies(_additionalDependencies); #endif + PublishProfileData(); } @@ -388,6 +393,8 @@ private void CompileMethodCleanup() _parameterIndexToNameMap = null; _localSlotToInfoMap = null; + + _additionalDependencies = null; #endif _debugLocInfos = null; _debugVarInfos = null; @@ -1315,6 +1322,8 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) { _compilation.NodeFactory.Resolver.AddModuleTokenForMethod(method, HandleToModuleToken(ref pResolvedToken)); } +#else + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, methodIL, method); #endif } else @@ -1341,6 +1350,8 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) { _compilation.NodeFactory.Resolver.AddModuleTokenForField(field, HandleToModuleToken(ref pResolvedToken)); } +#else + _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, methodIL, field); #endif } else @@ -3262,13 +3273,6 @@ private uint getJitFlags(ref CORJIT_FLAGS flags, uint sizeInBytes) if (this.MethodBeingCompiled.IsUnmanagedCallersOnly) { -#if READYTORUN - if (targetArchitecture == TargetArchitecture.X86) - { - throw new RequiresRuntimeJitException("ReadyToRun: Methods with UnmanagedCallersOnlyAttribute not implemented"); - } -#endif - // Validate UnmanagedCallersOnlyAttribute usage if (!this.MethodBeingCompiled.Signature.IsStatic) // Must be a static method { diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs index 511199c8edc66..5523fb66c0b69 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs @@ -1351,7 +1351,7 @@ public enum CorJitFlag : uint CORJIT_FLAG_SAMPLING_JIT_BACKGROUND = 35, // JIT is being invoked as a result of stack sampling for hot methods in the background CORJIT_FLAG_USE_PINVOKE_HELPERS = 36, // The JIT should use the PINVOKE_{BEGIN,END} helpers instead of emitting inline transitions CORJIT_FLAG_REVERSE_PINVOKE = 37, // The JIT should insert REVERSE_PINVOKE_{ENTER,EXIT} helpers into method prolog/epilog - CORJIT_FLAG_UNUSED10 = 38, + CORJIT_FLAG_TRACK_TRANSITIONS = 38, // The JIT should insert the helper variants that track transitions. CORJIT_FLAG_TIER0 = 39, // This is the initial tier for tiered compilation which should generate code as quickly as possible CORJIT_FLAG_TIER1 = 40, // This is the final tier (for now) for tiered compilation which should generate high quality code CORJIT_FLAG_RELATIVE_CODE_RELOCS = 41, // JIT should generate PC-relative address computations instead of EE relocation records diff --git a/src/coreclr/tools/Common/JitInterface/JitConfigProvider.cs b/src/coreclr/tools/Common/JitInterface/JitConfigProvider.cs index 94352584177ce..b1b56fb67de50 100644 --- a/src/coreclr/tools/Common/JitInterface/JitConfigProvider.cs +++ b/src/coreclr/tools/Common/JitInterface/JitConfigProvider.cs @@ -41,10 +41,10 @@ public static void Initialize( if (Interlocked.CompareExchange(ref s_instance, config, null) != null) throw new InvalidOperationException(); -#if READYTORUN NativeLibrary.SetDllImportResolver(typeof(CorInfoImpl).Assembly, (libName, assembly, searchPath) => { IntPtr libHandle = IntPtr.Zero; +#if READYTORUN if (libName == CorInfoImpl.JitLibrary) { if (!string.IsNullOrEmpty(jitPath)) @@ -56,15 +56,15 @@ public static void Initialize( libHandle = NativeLibrary.Load("clrjit_" + GetTargetSpec(target), assembly, searchPath); } } +#else + Debug.Assert(jitPath == null); +#endif if (libName == CorInfoImpl.JitSupportLibrary) { libHandle = NativeLibrary.Load("jitinterface_" + RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(), assembly, searchPath); } return libHandle; }); -#else - Debug.Assert(jitPath == null); -#endif CorInfoImpl.Startup(); } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs index 6c6c7ddecd9e6..a92cca2669999 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/DefType.FieldLayout.cs @@ -161,6 +161,27 @@ public LayoutInt InstanceByteAlignment } } + public bool IsZeroSizedReferenceType + { + get + { + if (Category != TypeFlags.Class) + { + throw new InvalidOperationException("Only reference types are allowed."); + } + + if (!_fieldLayoutFlags.HasFlags(FieldLayoutFlags.ComputedInstanceTypeLayout)) + { + ComputeInstanceLayout(InstanceLayoutKind.TypeOnly); + } + + // test that size without padding is zero: + // _instanceByteCountUnaligned - _instanceByteAlignment == LayoutInt.Zero + // simplified to: + return _instanceByteCountUnaligned == _instanceByteAlignment; + } + } + /// /// The type has stable Abi layout /// diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TargetArchitecture.cs b/src/coreclr/tools/Common/TypeSystem/Common/TargetArchitecture.cs new file mode 100644 index 0000000000000..b3b587d1432f4 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/TargetArchitecture.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 Debug = System.Diagnostics.Debug; + +namespace Internal.TypeSystem +{ + /// + /// Specifies the target CPU architecture. + /// + public enum TargetArchitecture + { + Unknown, + ARM, + ARM64, + X64, + X86, + Wasm32, + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs b/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs index 26858543adbb9..8bb69209cb731 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TargetDetails.cs @@ -6,19 +6,6 @@ namespace Internal.TypeSystem { - /// - /// Specifies the target CPU architecture. - /// - public enum TargetArchitecture - { - Unknown, - ARM, - ARM64, - X64, - X86, - Wasm32, - } - /// /// Specifies the target ABI. /// diff --git a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs index fea531003c3b8..1b901f5e623ed 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/Utilities/CustomAttributeTypeNameParser.cs @@ -69,7 +69,9 @@ public static TypeDesc GetTypeByCustomAttributeTypeName(this ModuleDesc module, AssemblyName homeAssembly = FindAssemblyIfNamePresent(name); if (homeAssembly != null) { - homeModule = module.Context.ResolveAssembly(homeAssembly); + homeModule = module.Context.ResolveAssembly(homeAssembly, throwIfNotFound); + if (homeModule == null) + return null; } MetadataType typeDef = resolver != null ? resolver(genericTypeDefName.ToString(), homeModule, throwIfNotFound) : ResolveCustomAttributeTypeDefinitionName(genericTypeDefName.ToString(), homeModule, throwIfNotFound); diff --git a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs index f713f9cb0c29f..84dcc80be1323 100644 --- a/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs +++ b/src/coreclr/tools/Common/TypeSystem/Interop/IL/MarshalUtils.cs @@ -18,12 +18,13 @@ public static bool IsBlittableType(TypeDesc type) return false; } - TypeDesc baseType = type.BaseType; + DefType baseType = type.BaseType; bool hasNonTrivialParent = baseType != null && !baseType.IsWellKnownType(WellKnownType.Object) && !baseType.IsWellKnownType(WellKnownType.ValueType); - if (hasNonTrivialParent && !IsBlittableType(baseType)) + // Type is blittable only if parent is also blittable and is not empty. + if (hasNonTrivialParent && (!IsBlittableType(baseType) || baseType.IsZeroSizedReferenceType)) { return false; } diff --git a/src/coreclr/tools/ILVerification/ILVerification.projitems b/src/coreclr/tools/ILVerification/ILVerification.projitems index 42f60611ee181..c68e48bdc552c 100644 --- a/src/coreclr/tools/ILVerification/ILVerification.projitems +++ b/src/coreclr/tools/ILVerification/ILVerification.projitems @@ -151,6 +151,9 @@ TypeSystem\Common\SignatureVariable.cs + + TypeSystem\Common\TargetArchitecture.cs + TypeSystem\Common\TargetDetails.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs new file mode 100644 index 0000000000000..eaf66f945dc67 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; + +using ILCompiler.IBC; + +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +namespace ILCompiler +{ + // This is a sample of the Json format the call chain data is stored in: + // + //{ + // "Microsoft.CodeAnalysis.CSharp.BinderFactory!GetBinder": [ + // [ + // "Microsoft.CodeAnalysis.CSharp.BinderFactory!GetBinder", + // "Microsoft.CodeAnalysis.CSharp.BinderFactory+BinderFactoryVisitor!VisitCompilationUnit" + // ], + // [ + // 1, + // 2 + // ] + // ], + // "Microsoft.CodeAnalysis.CSharp.BinderFactory+BinderFactoryVisitor!VisitCompilationUnit": [ + // [ + // "System.Lazy`1[System.__Canon]!CreateValue" + // ], + // [ + // 1 + // ] + // ], + //} + public class CallChainProfile + { + private readonly IEnumerable _referenceableModules; + private readonly Dictionary> _resolvedProfileData; + + // Diagnostics +#if DEBUG + private int _methodResolvesAttempted = 0; + private int _methodsSuccessfullyResolved = 0; + private Dictionary _resolveFails = new Dictionary(); +#endif + + public CallChainProfile(string callChainProfileFile, + CompilerTypeSystemContext context, + IEnumerable referenceableModules) + { + _referenceableModules = referenceableModules; + var analysisData = ReadCallChainAnalysisData(callChainProfileFile); + _resolvedProfileData = ResolveMethods(analysisData, context); + } + + /// + /// Try to resolve each name from the profile data to a MethodDesc + /// + private Dictionary> ResolveMethods(Dictionary> profileData, CompilerTypeSystemContext context) + { + var resolvedProfileData = new Dictionary>(); + Dictionary nameToMethodDescMap = new Dictionary(); + + foreach (var keyAndMethods in profileData) + { + // Resolve the calling method + var resolvedKeyMethod = CachedResolveMethodName(nameToMethodDescMap, keyAndMethods.Key, context); + + if (resolvedKeyMethod == null) + continue; + + // Resolve each callee and counts + foreach (var methodAndHitCount in keyAndMethods.Value) + { + var resolvedCalledMethod = CachedResolveMethodName(nameToMethodDescMap ,methodAndHitCount.Key, context); + if (resolvedCalledMethod == null) + continue; + + if (!resolvedProfileData.ContainsKey(resolvedKeyMethod)) + { + resolvedProfileData.Add(resolvedKeyMethod, new Dictionary()); + } + + if (!resolvedProfileData[resolvedKeyMethod].ContainsKey(resolvedCalledMethod)) + { + resolvedProfileData[resolvedKeyMethod].Add(resolvedCalledMethod, 0); + } + resolvedProfileData[resolvedKeyMethod][resolvedCalledMethod] += methodAndHitCount.Value; + } + } + + return resolvedProfileData; + } + + private MethodDesc CachedResolveMethodName(Dictionary nameToMethodDescMap, string methodName, CompilerTypeSystemContext context) + { + MethodDesc resolvedMethod = null; + if (nameToMethodDescMap.ContainsKey(methodName)) + { + resolvedMethod = nameToMethodDescMap[methodName]; + } + else + { + resolvedMethod = ResolveMethodName(context, methodName); + nameToMethodDescMap.Add(methodName, resolvedMethod); + } + +#if DEBUG + if (resolvedMethod == null) + { + if (!_resolveFails.ContainsKey(methodName)) + { + _resolveFails.Add(methodName, 0); + } + _resolveFails[methodName]++; + } +#endif + return resolvedMethod; + } + + private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, string methodName) + { + // Example method name entries. Can we parse them as custom attribute formatted names? + // mscorlib.ni.dll!System.Runtime.ExceptionServices.ExceptionDispatchInfo..ctor + // System.Core.ni.dll!System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.__Canon,System.__Canon].MoveNext + // Microsoft.Azure.Monitoring.WarmPath.FrontEnd.Middleware.SecurityMiddlewareBase`1+d__6[System.__Canon]!MoveNext + // System.Runtime.CompilerServices.AsyncTaskMethodBuilder!Start +#if DEBUG + _methodResolvesAttempted++; +#endif + + string[] splitMethodName = methodName.Split("!"); + if (splitMethodName.Length != 2) + { + return null; + } + + if (splitMethodName[0].EndsWith(".dll") || + splitMethodName[0].EndsWith(".ni.dll") || + splitMethodName[0].EndsWith(".exe") || + splitMethodName[0].EndsWith(".ni.exe")) + { + // Native stack frame for the method name. This happens for managed methods in native images + // (Remember, this is .NET Framework data we're starting with) + string moduleSimpleName = Path.ChangeExtension(splitMethodName[0], null); + // Desktop has native images with ni.dll or ni.exe extensions very frequently + if (moduleSimpleName.EndsWith(".ni")) + moduleSimpleName = moduleSimpleName.Substring(0, moduleSimpleName.Length - 3); + string unresolvedNamespaceTypeAndMethodName = splitMethodName[1]; + + // Try to resolve the module from the list of loaded assemblies + EcmaModule resolvedModule = context.GetModuleForSimpleName(moduleSimpleName, false); + if (resolvedModule == null) + return null; + + // Resolve a name like System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.__Canon,System.__Canon].MoveNext + // Take the string after the last period as the method name (special case for .ctor and .cctor) + string namespaceAndTypeName = null; + string methodNameWithoutType = null; + + if (unresolvedNamespaceTypeAndMethodName.EndsWith("..ctor")) + { + namespaceAndTypeName = unresolvedNamespaceTypeAndMethodName.Substring(0, unresolvedNamespaceTypeAndMethodName.Length - "..ctor".Length); + methodNameWithoutType = ".ctor"; + } + else if (unresolvedNamespaceTypeAndMethodName.EndsWith("..cctor")) + { + namespaceAndTypeName = unresolvedNamespaceTypeAndMethodName.Substring(0, unresolvedNamespaceTypeAndMethodName.Length - "..cctor".Length); + methodNameWithoutType = ".cctor"; + } + else + { + int lastDotIndex = unresolvedNamespaceTypeAndMethodName.LastIndexOf("."); + if (lastDotIndex < 0) + return null; + + namespaceAndTypeName = unresolvedNamespaceTypeAndMethodName.Substring(0, lastDotIndex); + methodNameWithoutType = unresolvedNamespaceTypeAndMethodName.Length > lastDotIndex ? unresolvedNamespaceTypeAndMethodName.Substring(lastDotIndex + 1) : ""; + } + + var resolvedMethod = ResolveMethodName(context, resolvedModule, namespaceAndTypeName, methodNameWithoutType); + if (resolvedMethod != null) + { +#if DEBUG + _methodsSuccessfullyResolved++; +#endif + return resolvedMethod; + } + + } + else + { + // We have Namespace.Type!Method format with no method signature information. Check all loaded modules for a matching + // type name, and the first method on that type with matching name. + // Microsoft.Azure.Monitoring.WarmPath.FrontEnd.Middleware.SecurityMiddlewareBase`1+d__6[System.__Canon]!MoveNext + // System.Runtime.CompilerServices.AsyncTaskMethodBuilder!Start + string namespaceAndTypeName = splitMethodName[0]; + string methodNameWithoutType = splitMethodName[1]; + + foreach (var module in _referenceableModules) + { + var resolvedMethod = ResolveMethodName(context, module, namespaceAndTypeName, methodNameWithoutType); + if (resolvedMethod != null) + { +#if DEBUG + _methodsSuccessfullyResolved++; +#endif + return resolvedMethod; + } + + } + } + + return null; + } + + /// + /// Given a parsed out module, namespace + type, and method name, try to find a matching MethodDesc + /// TODO: We have no signature information for the method - what policy should we apply where multiple methods exist with the same name + /// but different signatures? For now we'll take the first matching and ignore others. Ideally we'll improve the profile data to include this. + /// + /// MethodDesc if found, null otherwise + private MethodDesc ResolveMethodName(CompilerTypeSystemContext context, ModuleDesc module, string namespaceAndTypeName, string methodName) + { + TypeDesc resolvedType = module.GetTypeByCustomAttributeTypeName(namespaceAndTypeName, false, (typeDefName, module, throwIfNotFound) => + { + return (MetadataType)context.GetCanonType(typeDefName) + ?? CustomAttributeTypeNameParser.ResolveCustomAttributeTypeDefinitionName(typeDefName, module, throwIfNotFound); + }); + + if (resolvedType != null) + { + var resolvedMethod = resolvedType.GetMethod(methodName, null); + if (resolvedMethod != null) + { + return resolvedMethod; + } + } + + return null; + } + + private Dictionary> ReadCallChainAnalysisData(string jsonProfileFile) + { + Dictionary> profileData = new Dictionary>(); + + using (StreamReader stream = File.OpenText(jsonProfileFile)) + using (JsonDocument document = JsonDocument.Parse(stream.BaseStream)) + { + JsonElement root = document.RootElement; + + foreach (JsonProperty methodAndCallees in root.EnumerateObject()) + { + string keyParts = methodAndCallees.Name; + bool readingMethodNames = true; + List followingMethodList = new List(); + foreach (JsonElement methodListArray in methodAndCallees.Value.EnumerateArray()) + { + // This loop iterates twice: once for the callee method names, and again for a parallel list of call counts + if (readingMethodNames) + { + foreach (JsonElement followingMethods in methodListArray.EnumerateArray()) + { + followingMethodList.Add(followingMethods.GetString()); + } + + readingMethodNames = false; + } + else + { + // Read the array of call counts + int index = 0; + foreach (JsonElement methodCount in methodListArray.EnumerateArray()) + { + if (string.IsNullOrEmpty(keyParts)) + break; + + if (!profileData.ContainsKey(keyParts)) + { + profileData.Add(keyParts, new Dictionary()); + } + if (!profileData[keyParts].ContainsKey(followingMethodList[index])) + { + profileData[keyParts].Add(followingMethodList[index], methodCount.GetInt32()); + } + else + { + profileData[keyParts][followingMethodList[index]] += methodCount.GetInt32(); + } + index++; + } + } + } + } + } + return profileData; + } + +#if DEBUG + /// + /// Dump diagnostic information to the console + /// + private void WriteProfileParseStats() + { + Console.WriteLine("Callchain profile entries:"); + + // Display all resolved methods in key -> { method -> count, method2 -> count} map + foreach (var key in _resolvedProfileData) + { + Console.WriteLine($"{key.Key.ToString()}"); + + foreach (var calledMethodAndCount in key.Value) + { + Console.WriteLine($"\t{calledMethodAndCount.Key.ToString()} -> {calledMethodAndCount.Value} calls"); + } + } + + Console.WriteLine($"Method resolves attempted: {_methodResolvesAttempted}"); + Console.WriteLine($"Successfully resolved {_methodsSuccessfullyResolved} methods ({(double)_methodsSuccessfullyResolved / (double)_methodResolvesAttempted:P})"); + } +#endif + } + +} + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index e6f136eb22377..9b4dba1e72480 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -83,6 +83,7 @@ + @@ -91,12 +92,15 @@ + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index 4cd0a16083ba6..19ce426374da1 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -195,7 +195,7 @@ public bool Equals(MethodWithToken methodWithToken) Debug.Assert(OwningTypeNotDerivedFromToken == methodWithToken.OwningTypeNotDerivedFromToken); Debug.Assert(OwningType == methodWithToken.OwningType); } - + return equals; } @@ -1477,7 +1477,7 @@ private void ceeInfoGetCallInfo( bool devirt; // Check For interfaces before the bubble check - // since interface methods shouldnt change from non-virtual to virtual between versions + // since interface methods shouldnt change from non-virtual to virtual between versions if (targetMethod.OwningType.IsInterface) { // Handle interface methods specially because the Sealed bit has no meaning on interfaces. @@ -1496,7 +1496,7 @@ private void ceeInfoGetCallInfo( // check the Typical TargetMethod, not the Instantiation !_compilation.NodeFactory.CompilationModuleGroup.VersionsWithMethodBody(targetMethod.GetTypicalMethodDefinition())) { - // For version resiliency we won't de-virtualize all final/sealed method calls. Because during a + // For version resiliency we won't de-virtualize all final/sealed method calls. Because during a // servicing event it is legal to unseal a method or type. // // Note that it is safe to devirtualize in the following cases, since a servicing event cannot later modify it @@ -1554,7 +1554,7 @@ private void ceeInfoGetCallInfo( // (a) some JITs may call instantiating stubs (it makes the JIT simpler) and // (b) if the method is a remote stub then the EE will force the // call through an instantiating stub and - // (c) constraint calls that require runtime context lookup are never resolved + // (c) constraint calls that require runtime context lookup are never resolved // to underlying shared generic code const CORINFO_CALLINFO_FLAGS LdVirtFtnMask = CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_LDFTN | CORINFO_CALLINFO_FLAGS.CORINFO_CALLINFO_CALLVIRT; @@ -1627,7 +1627,7 @@ private void ceeInfoGetCallInfo( } else { - // At this point, we knew it is a virtual call to targetMethod, + // At this point, we knew it is a virtual call to targetMethod, // If it is also a default interface method call, it should go through instantiating stub. useInstantiatingStub = useInstantiatingStub || (targetMethod.OwningType.IsInterface && !originalMethod.IsAbstract); // Insert explicit null checks for cross-version bubble non-interface calls. @@ -1735,26 +1735,20 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO EcmaModule callerModule; bool useInstantiatingStub; ceeInfoGetCallInfo( - ref pResolvedToken, - pConstrainedResolvedToken, - callerHandle, - flags, - pResult, + ref pResolvedToken, + pConstrainedResolvedToken, + callerHandle, + flags, + pResult, out methodToCall, - out targetMethod, - out constrainedType, - out originalMethod, + out targetMethod, + out constrainedType, + out originalMethod, out exactType, out callerMethod, out callerModule, out useInstantiatingStub); - var targetDetails = _compilation.TypeSystemContext.Target; - if (targetDetails.Architecture == TargetArchitecture.X86 && targetMethod.IsUnmanagedCallersOnly) - { - throw new RequiresRuntimeJitException("ReadyToRun: References to methods with UnmanagedCallersOnlyAttribute not implemented"); - } - if (pResult->thisTransform == CORINFO_THIS_TRANSFORM.CORINFO_BOX_THIS) { // READYTORUN: FUTURE: Optionally create boxing stub at runtime @@ -1831,7 +1825,7 @@ private void getCallInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_RESO break; case CORINFO_CALL_KIND.CORINFO_VIRTUALCALL_VTABLE: - // Only calls within the CoreLib version bubble support fragile NI codegen with vtable based calls, for better performance (because + // Only calls within the CoreLib version bubble support fragile NI codegen with vtable based calls, for better performance (because // CoreLib and the runtime will always be updated together anyways - this is a special case) // Eagerly check abi stability here as no symbol usage can be used to delay the check @@ -1912,7 +1906,7 @@ private void ComputeRuntimeLookupForSharedGenericToken( MethodDesc contextMethod = methodFromContext(pResolvedToken.tokenContext); TypeDesc contextType = typeFromContext(pResolvedToken.tokenContext); - // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup. + // There is a pathological case where invalid IL refereces __Canon type directly, but there is no dictionary availabled to store the lookup. if (!contextMethod.IsSharedByGenericInstantiations) { ThrowHelper.ThrowInvalidProgramException(); @@ -1968,7 +1962,7 @@ private void ComputeRuntimeLookupForSharedGenericToken( throw new NotImplementedException(entryKind.ToString()); } - // For R2R compilations, we don't generate the dictionary lookup signatures (dictionary lookups are done in a + // For R2R compilations, we don't generate the dictionary lookup signatures (dictionary lookups are done in a // different way that is more version resilient... plus we can't have pointers to existing MTs/MDs in the sigs) } @@ -2441,7 +2435,7 @@ private bool canGetCookieForPInvokeCalliSig(CORINFO_SIG_INFO* szMetaSig) } private int SizeOfPInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_PInvokeTransitionFrameSizeInPointerUnits * _compilation.NodeFactory.Target.PointerSize; - private int SizeOfReversePInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits * _compilation.NodeFactory.Target.PointerSize; + private int SizeOfReversePInvokeTransitionFrame => ReadyToRunRuntimeConstants.READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits(_compilation.NodeFactory.Target.Architecture) * _compilation.NodeFactory.Target.PointerSize; private void setEHcount(uint cEH) { diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj index 8e464d35ec830..94d1104541c27 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ILCompiler.Reflection.ReadyToRun.csproj @@ -27,5 +27,6 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/CoreTestAssembly/Marshalling.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/CoreTestAssembly/Marshalling.cs new file mode 100644 index 0000000000000..4ddf5702b2b46 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/CoreTestAssembly/Marshalling.cs @@ -0,0 +1,183 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace Marshalling +{ + + #region Simple classes + + public class SimpleEmptyClass + { + } + + public class SimpleByteClass + { + public byte x; + } + + public class SimpleInt16Class + { + public short x; + } + + public class SimpleInt32Class + { + public int x; + } + + public class SimpleInt64Class + { + public long x; + } + + #endregion + + #region LayoutKind.Explicit classes + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitEmptyBase + { + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitEmptyBase : ExplicitEmptyBase + { + [FieldOffset(0)] + public int i; + } + + [StructLayout(LayoutKind.Explicit, Size = 0)] + public class ExplicitEmptySizeZeroBase + { + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitEmptySizeZeroBase : ExplicitEmptySizeZeroBase + { + [FieldOffset(0)] + public int i; + } + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitByteBase + { + [FieldOffset(0)] + public byte x; + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitByteBase : ExplicitByteBase + { + [FieldOffset(1)] + public int i; + } + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitInt16Base + { + [FieldOffset(0)] + public short x; + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitInt16Base : ExplicitInt16Base + { + [FieldOffset(1)] + public int i; + } + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitInt32Base + { + [FieldOffset(0)] + public int x; + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitInt32Base : ExplicitInt32Base + { + [FieldOffset(1)] + public int i; + } + + [StructLayout(LayoutKind.Explicit)] + public class ExplicitInt64Base + { + [FieldOffset(0)] + public long x; + } + + [StructLayout(LayoutKind.Explicit)] + public class ClassWithExplicitInt64Base : ExplicitInt64Base + { + [FieldOffset(1)] + public int i; + } + + [StructLayout(LayoutKind.Sequential)] + public class SequentialEmptyBase + { + } + + #endregion + + #region LayoutKind.Sequential classes + + [StructLayout(LayoutKind.Sequential)] + public class ClassWithSequentialEmptyBase : SequentialEmptyBase + { + public int i; + } + + [StructLayout(LayoutKind.Sequential)] + public class SequentialByteBase + { + public byte x; + } + + [StructLayout(LayoutKind.Sequential)] + public class ClassWithSequentialByteBase : SequentialByteBase + { + public int i; + } + + [StructLayout(LayoutKind.Sequential)] + public class SequentialInt16Base + { + public short x; + } + + [StructLayout(LayoutKind.Sequential)] + public class ClassWithSequentialInt16Base : SequentialInt16Base + { + public int i; + } + + [StructLayout(LayoutKind.Sequential)] + public class SequentialInt32Base + { + public int x; + } + + [StructLayout(LayoutKind.Sequential)] + public class ClassWithSequentialInt32Base : SequentialInt32Base + { + public int i; + } + + [StructLayout(LayoutKind.Sequential)] + public class SequentialInt64Base + { + public long x; + } + + [StructLayout(LayoutKind.Sequential)] + public class ClassWithSequentialInt64Base : SequentialInt64Base + { + public int i; + } + + #endregion +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/DefType.FieldLayoutTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/DefType.FieldLayoutTests.cs new file mode 100644 index 0000000000000..c82f6c21a85be --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/DefType.FieldLayoutTests.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.TypeSystem; +using Internal.TypeSystem.Interop; + +using Xunit; + +namespace TypeSystemTests +{ + public partial class DefTypeTests + { + private ModuleDesc _testModule; + + public DefTypeTests() + { + var context = new TestTypeSystemContext(TargetArchitecture.X64); + var systemModule = context.CreateModuleForSimpleName("CoreTestAssembly"); + context.SetSystemModule(systemModule); + + _testModule = systemModule; + } + + [Theory] + [InlineData("SimpleByteClass")] + [InlineData("SimpleInt16Class")] + [InlineData("SimpleInt32Class")] + [InlineData("SimpleInt64Class")] + [InlineData("ExplicitByteBase")] + [InlineData("ExplicitInt16Base")] + [InlineData("ExplicitInt32Base")] + [InlineData("ExplicitInt64Base")] + [InlineData("SequentialByteBase")] + [InlineData("SequentialInt16Base")] + [InlineData("SequentialInt32Base")] + [InlineData("SequentialInt64Base")] + public void IsZeroSizedReferenceType_NonEmptyType_ReturnsFalse(string className) + { + DefType nonEmptyClass = _testModule.GetType("Marshalling", className); + Assert.False(nonEmptyClass.IsZeroSizedReferenceType); + } + + [Theory] + [InlineData("SimpleEmptyClass")] + [InlineData("ExplicitEmptyBase")] + [InlineData("ExplicitEmptySizeZeroBase")] + [InlineData("SequentialEmptyBase")] + public void IsZeroSizedReferenceType_EmptyType_ReturnsTrue(string className) + { + DefType emptyClass = _testModule.GetType("Marshalling", className); + Assert.True(emptyClass.IsZeroSizedReferenceType); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/ILCompiler.TypeSystem.ReadyToRun.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/ILCompiler.TypeSystem.ReadyToRun.Tests.csproj index 21f6f8a1370ed..9b5fec9b38065 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/ILCompiler.TypeSystem.ReadyToRun.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/ILCompiler.TypeSystem.ReadyToRun.Tests.csproj @@ -21,6 +21,7 @@ + @@ -41,6 +42,7 @@ + @@ -58,5 +60,6 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/MarshalUtilsTests.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/MarshalUtilsTests.cs new file mode 100644 index 0000000000000..c3befa87af72d --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/MarshalUtilsTests.cs @@ -0,0 +1,83 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Internal.TypeSystem; +using Internal.TypeSystem.Interop; + +using Xunit; + +namespace TypeSystemTests +{ + public class MarshalUtilsTests + { + private TestTypeSystemContext _context; + private ModuleDesc _testModule; + + public MarshalUtilsTests() + { + _context = new TestTypeSystemContext(TargetArchitecture.X64); + var systemModule = _context.CreateModuleForSimpleName("CoreTestAssembly"); + _context.SetSystemModule(systemModule); + + _testModule = systemModule; + } + + [Theory] + [InlineData(WellKnownType.Void)] + [InlineData(WellKnownType.Boolean)] + [InlineData(WellKnownType.Char)] + [InlineData(WellKnownType.SByte)] + [InlineData(WellKnownType.Byte)] + [InlineData(WellKnownType.Int16)] + [InlineData(WellKnownType.UInt16)] + [InlineData(WellKnownType.Int32)] + [InlineData(WellKnownType.UInt32)] + [InlineData(WellKnownType.Int64)] + [InlineData(WellKnownType.UInt64)] + [InlineData(WellKnownType.IntPtr)] + [InlineData(WellKnownType.UIntPtr)] + [InlineData(WellKnownType.Single)] + [InlineData(WellKnownType.Double)] + [InlineData(WellKnownType.RuntimeFieldHandle)] + [InlineData(WellKnownType.RuntimeTypeHandle)] + [InlineData(WellKnownType.RuntimeMethodHandle)] + public void IsBlittableType_BilittableWellKnownTypes_ReturnsTrue(WellKnownType type) => + Assert.True(MarshalUtils.IsBlittableType(_context.GetWellKnownType(type))); + + [Theory] + [InlineData(WellKnownType.String)] + [InlineData(WellKnownType.ValueType)] + [InlineData(WellKnownType.Enum)] + [InlineData(WellKnownType.Array)] + [InlineData(WellKnownType.MulticastDelegate)] + [InlineData(WellKnownType.Exception)] + [InlineData(WellKnownType.Object)] + public void IsBlittableType_NonBilittableWellKnownTypes_ReturnsFalse(WellKnownType type) => + Assert.False(MarshalUtils.IsBlittableType(_context.GetWellKnownType(type))); + + [Theory] + [InlineData("ClassWithExplicitByteBase")] + [InlineData("ClassWithExplicitInt16Base")] + [InlineData("ClassWithExplicitInt32Base")] + [InlineData("ClassWithExplicitInt64Base")] + [InlineData("ClassWithSequentialByteBase")] + [InlineData("ClassWithSequentialInt16Base")] + [InlineData("ClassWithSequentialInt32Base")] + [InlineData("ClassWithSequentialInt64Base")] + public void IsBlittableType_TypeWithBlittableBase_ReturnsTrue(string className) + { + TypeDesc classWithBlittableBase = _testModule.GetType("Marshalling", className); + Assert.True(MarshalUtils.IsBlittableType(classWithBlittableBase)); + } + + [Theory] + [InlineData("ClassWithExplicitEmptyBase")] + [InlineData("ClassWithExplicitEmptySizeZeroBase")] + [InlineData("ClassWithSequentialEmptyBase")] + public void IsBlittableType_TypeWithEmptyBase_ReturnsFalse(string className) + { + TypeDesc classWithEmptyBase = _testModule.GetType("Marshalling", className); + Assert.False(MarshalUtils.IsBlittableType(classWithEmptyBase)); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj index 4323ffe0e4893..c8e83d94bf848 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.ReadyToRun/ILCompiler.TypeSystem.ReadyToRun.csproj @@ -278,6 +278,9 @@ TypeSystem\Common\SignatureVariable.cs + + TypeSystem\Common\TargetArchitecture.cs + TypeSystem\Common\TargetDetails.cs diff --git a/src/coreclr/tools/aot/crossgen2/CommandLineOptions.cs b/src/coreclr/tools/aot/crossgen2/CommandLineOptions.cs index 910743f709b15..1c15eaa23f488 100644 --- a/src/coreclr/tools/aot/crossgen2/CommandLineOptions.cs +++ b/src/coreclr/tools/aot/crossgen2/CommandLineOptions.cs @@ -55,6 +55,7 @@ internal class CommandLineOptions public string MethodLayout; public string FileLayout; public bool VerifyTypeAndFieldLayout; + public string CallChainProfileFile; public string SingleMethodTypeName; public string SingleMethodName; @@ -129,6 +130,7 @@ public CommandLineOptions(string[] args) syntax.DefineOption("method-layout", ref MethodLayout, SR.MethodLayoutOption); syntax.DefineOption("file-layout", ref FileLayout, SR.FileLayoutOption); syntax.DefineOption("verify-type-and-field-layout", ref VerifyTypeAndFieldLayout, SR.VerifyTypeAndFieldLayoutOption); + syntax.DefineOption("callchain-profile", ref CallChainProfileFile, SR.CallChainProfileFile); syntax.DefineOption("h|help", ref Help, SR.HelpOption); diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 35dbcecfe593c..d6c7dbff2b139 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -523,6 +523,14 @@ private int Run(string[] args) _commandLineOptions.CompileBubbleGenerics); } + // Load any profiles generated by method call chain analyis + CallChainProfile jsonProfile = null; + + if (!string.IsNullOrEmpty(_commandLineOptions.CallChainProfileFile)) + { + jsonProfile = new CallChainProfile(_commandLineOptions.CallChainProfileFile, _typeSystemContext, referenceableModules); + } + // Examine profile guided information as appropriate ProfileDataManager profileDataManager = new ProfileDataManager(logger, diff --git a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx index 6b3e6bec1265c..a0b7c2e402678 100644 --- a/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx +++ b/src/coreclr/tools/aot/crossgen2/Properties/Resources.resx @@ -309,6 +309,9 @@ Warning: -Od overrides other optimization options + + Json file(s) for predictive profile guided optimization + Generate PDB symbol information file (supported on Windows only) diff --git a/src/coreclr/tools/aot/jitinterface/CMakeLists.txt b/src/coreclr/tools/aot/jitinterface/CMakeLists.txt index faba8d685c9e8..9b7640e1a49b8 100644 --- a/src/coreclr/tools/aot/jitinterface/CMakeLists.txt +++ b/src/coreclr/tools/aot/jitinterface/CMakeLists.txt @@ -7,9 +7,14 @@ set(NATIVE_SOURCES corinfoexception.cpp ) +if(CLR_CMAKE_TARGET_WIN32) + set(JITINTERFACE_RESOURCES Native.rc) +endif(CLR_CMAKE_TARGET_WIN32) + add_library_clr(jitinterface_${ARCH_HOST_NAME} SHARED ${NATIVE_SOURCES} + ${JITINTERFACE_RESOURCES} ) install_clr(TARGETS jitinterface_${ARCH_HOST_NAME}) diff --git a/src/coreclr/tools/aot/jitinterface/Native.rc b/src/coreclr/tools/aot/jitinterface/Native.rc new file mode 100644 index 0000000000000..73b9adfbbfdd0 --- /dev/null +++ b/src/coreclr/tools/aot/jitinterface/Native.rc @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#define FX_VER_FILEDESCRIPTION_STR "Microsoft .NET Runtime Just-In-Time Compiler Type System Interface\0" + +#include +#include diff --git a/src/coreclr/vm/amd64/cgencpu.h b/src/coreclr/vm/amd64/cgencpu.h index c7ecc88d2c4d2..7312ad0a019fe 100644 --- a/src/coreclr/vm/amd64/cgencpu.h +++ b/src/coreclr/vm/amd64/cgencpu.h @@ -101,6 +101,8 @@ EXTERN_C void FastCallFinalizeWorker(Object *obj, PCODE funcPtr); #define X86RegFromAMD64Reg(extended_reg) \ ((X86Reg)(((int)extended_reg) & X86_REGISTER_MASK)) +#define FLOAT_REGISTER_SIZE 16 // each register in FloatArgumentRegisters is 16 bytes. + // Why is the return value ARG_SLOT? On 64-bit systems, that is 64-bits // and much bigger than necessary for R4, requiring explicit downcasts. inline @@ -145,11 +147,11 @@ void R8ToFPSpill(void* pSpillSlot, SIZE_T srcDoubleAsSIZE_T) // Parameter size //********************************************************************** -typedef INT64 StackElemType; -#define STACK_ELEM_SIZE sizeof(StackElemType) - -// !! This expression assumes STACK_ELEM_SIZE is a power of 2. -#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) +inline unsigned StackElemSize(unsigned parmSize, bool isValueType = false /* unused */, bool isFloatHfa = false /* unused */) +{ + const unsigned stackSlotSize = 8; + return ALIGN_UP(parmSize, stackSlotSize); +} //********************************************************************** // Frames diff --git a/src/coreclr/vm/arm/cgencpu.h b/src/coreclr/vm/arm/cgencpu.h index 3739f4ce5dc66..c68e763e8945d 100644 --- a/src/coreclr/vm/arm/cgencpu.h +++ b/src/coreclr/vm/arm/cgencpu.h @@ -95,15 +95,17 @@ EXTERN_C void setFPReturn(int fpSize, INT64 retVal); // Offset of pc register #define PC_REG_RELATIVE_OFFSET 4 +#define FLOAT_REGISTER_SIZE 4 // each register in FloatArgumentRegisters is 4 bytes. + //********************************************************************** // Parameter size //********************************************************************** -typedef INT32 StackElemType; -#define STACK_ELEM_SIZE sizeof(StackElemType) - -// !! This expression assumes STACK_ELEM_SIZE is a power of 2. -#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) +inline unsigned StackElemSize(unsigned parmSize, bool isValueType = false /* unused */, bool isFloatHfa = false /* unused */) +{ + const unsigned stackSlotSize = 4; + return ALIGN_UP(parmSize, stackSlotSize); +} //********************************************************************** // Frames diff --git a/src/coreclr/vm/arm64/cgencpu.h b/src/coreclr/vm/arm64/cgencpu.h index b28ac4d99c9fe..c87dbb11601f8 100644 --- a/src/coreclr/vm/arm64/cgencpu.h +++ b/src/coreclr/vm/arm64/cgencpu.h @@ -58,6 +58,8 @@ extern PCODE GetPreStubEntryPoint(); #define CALLDESCR_FPARGREGS 1 // CallDescrWorker has FloatArgumentRegisters parameter #define CALLDESCR_RETBUFFARGREG 1 // CallDescrWorker has RetBuffArg parameter that's separate from arg regs +#define FLOAT_REGISTER_SIZE 16 // each register in FloatArgumentRegisters is 16 bytes. + // Given a return address retrieved during stackwalk, // this is the offset by which it should be decremented to arrive at the callsite. #define STACKWALK_CONTROLPC_ADJUST_OFFSET 4 @@ -81,13 +83,27 @@ void R8ToFPSpill(void* pSpillSlot, SIZE_T srcDoubleAsSIZE_T) // Parameter size //********************************************************************** -typedef INT64 StackElemType; -#define STACK_ELEM_SIZE sizeof(StackElemType) - -// The expression below assumes STACK_ELEM_SIZE is a power of 2, so check that. -static_assert(((STACK_ELEM_SIZE & (STACK_ELEM_SIZE-1)) == 0), "STACK_ELEM_SIZE must be a power of 2"); +inline unsigned StackElemSize(unsigned parmSize, bool isValueType, bool isFloatHfa) +{ +#if defined(OSX_ARM64_ABI) + if (!isValueType) + { + // The primitive types' sizes are expected to be powers of 2. + _ASSERTE((parmSize & (parmSize - 1)) == 0); + // No padding/alignment for primitive types. + return parmSize; + } + if (isFloatHfa) + { + _ASSERTE((parmSize % 4) == 0); + // float hfa is not considered a struct type and passed with 4-byte alignment. + return parmSize; + } +#endif -#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) + const unsigned stackSlotSize = 8; + return ALIGN_UP(parmSize, stackSlotSize); +} // // JIT HELPERS. diff --git a/src/coreclr/vm/callhelpers.cpp b/src/coreclr/vm/callhelpers.cpp index 492869f1c328f..d5493b661bc32 100644 --- a/src/coreclr/vm/callhelpers.cpp +++ b/src/coreclr/vm/callhelpers.cpp @@ -517,7 +517,8 @@ void MethodDescCallSite::CallTargetWorker(const ARG_SLOT *pArguments, ARG_SLOT * CallDescrData callDescrData; callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); - callDescrData.numStackSlots = nStackBytes / STACK_ELEM_SIZE; + _ASSERTE((nStackBytes % TARGET_POINTER_SIZE) == 0); + callDescrData.numStackSlots = nStackBytes / TARGET_POINTER_SIZE; #ifdef CALLDESCR_ARGREGS callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); #endif diff --git a/src/coreclr/vm/callingconvention.h b/src/coreclr/vm/callingconvention.h index 129970032a0a4..8a1e934d3d78d 100644 --- a/src/coreclr/vm/callingconvention.h +++ b/src/coreclr/vm/callingconvention.h @@ -39,8 +39,8 @@ struct ArgLocDesc int m_idxGenReg; // First general register used (or -1) int m_cGenReg; // Count of general registers used (or 0) - int m_idxStack; // First stack slot used (or -1) - int m_cStack; // Count of stack slots used (or 0) + int m_byteStackIndex; // Stack offset in bytes (or -1) + int m_byteStackSize; // Stack size in bytes #if defined(UNIX_AMD64_ABI) @@ -85,8 +85,8 @@ struct ArgLocDesc m_cFloatReg = 0; m_idxGenReg = -1; m_cGenReg = 0; - m_idxStack = -1; - m_cStack = 0; + m_byteStackIndex = -1; + m_byteStackSize = 0; #if defined(TARGET_ARM) m_fRequires64BitAlignment = FALSE; #endif @@ -218,14 +218,23 @@ struct TransitionBlock #if defined(UNIX_AMD64_ABI) _ASSERTE(offset != TransitionBlock::StructInRegsOffset); #endif - return (offset - GetOffsetOfArgumentRegisters()) / TARGET_POINTER_SIZE; + offset -= GetOffsetOfArgumentRegisters(); + _ASSERTE((offset % TARGET_POINTER_SIZE) == 0); + return offset / TARGET_POINTER_SIZE; } static UINT GetStackArgumentIndexFromOffset(int offset) { LIMITED_METHOD_CONTRACT; - return (offset - TransitionBlock::GetOffsetOfArgs()) / STACK_ELEM_SIZE; + return (offset - TransitionBlock::GetOffsetOfArgs()) / TARGET_POINTER_SIZE; + } + + static UINT GetStackArgumentByteIndexFromOffset(int offset) + { + LIMITED_METHOD_CONTRACT; + + return (offset - TransitionBlock::GetOffsetOfArgs()); } #ifdef CALLDESCR_FPARGREGS @@ -319,6 +328,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE if (!(m_dwFlags & SIZE_OF_ARG_STACK_COMPUTED)) ForceSigWalk(); _ASSERTE((m_dwFlags & SIZE_OF_ARG_STACK_COMPUTED) != 0); + _ASSERTE((m_nSizeOfArgStack % TARGET_POINTER_SIZE) == 0); return m_nSizeOfArgStack; } @@ -336,7 +346,7 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE // The argument registers are not included in the stack size on AMD64 size += ARGUMENTREGISTERS_SIZE; #endif - + _ASSERTE((size % TARGET_POINTER_SIZE) == 0); return size; } @@ -595,17 +605,16 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE pLoc->Init(); - int cSlots = (GetArgSize() + 3) / 4; if (!TransitionBlock::IsStackArgumentOffset(argOffset)) { pLoc->m_idxGenReg = TransitionBlock::GetArgumentIndexFromOffset(argOffset); - _ASSERTE(cSlots == 1); - pLoc->m_cGenReg = cSlots; + _ASSERTE(GetArgSize() <= TARGET_POINTER_SIZE); + pLoc->m_cGenReg = 1; } else { - pLoc->m_idxStack = TransitionBlock::GetStackArgumentIndexFromOffset(argOffset); - pLoc->m_cStack = cSlots; + pLoc->m_byteStackSize = GetArgSize(); + pLoc->m_byteStackIndex = TransitionBlock::GetStackArgumentByteIndexFromOffset(argOffset); } } #endif @@ -620,12 +629,13 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE pLoc->m_fRequires64BitAlignment = m_fRequires64BitAlignment; - int cSlots = (GetArgSize() + 3) / 4; - + const int byteArgSize = GetArgSize(); if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) { - pLoc->m_idxFloatReg = (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()) / 4; - pLoc->m_cFloatReg = cSlots; + const int floatRegOfsInBytes = argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters(); + _ASSERTE((floatRegOfsInBytes % FLOAT_REGISTER_SIZE) == 0); + pLoc->m_idxFloatReg = floatRegOfsInBytes / FLOAT_REGISTER_SIZE; + pLoc->m_cFloatReg = ALIGN_UP(byteArgSize, FLOAT_REGISTER_SIZE) / FLOAT_REGISTER_SIZE; return; } @@ -633,22 +643,21 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE { pLoc->m_idxGenReg = TransitionBlock::GetArgumentIndexFromOffset(argOffset); - if (cSlots <= (4 - pLoc->m_idxGenReg)) + if (byteArgSize <= (4 - pLoc->m_idxGenReg) * TARGET_POINTER_SIZE) { - pLoc->m_cGenReg = cSlots; + pLoc->m_cGenReg = ALIGN_UP(byteArgSize, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; } else { pLoc->m_cGenReg = 4 - pLoc->m_idxGenReg; - - pLoc->m_idxStack = 0; - pLoc->m_cStack = cSlots - pLoc->m_cGenReg; + pLoc->m_byteStackIndex = 0; + pLoc->m_byteStackSize = StackElemSize(byteArgSize) - pLoc->m_cGenReg * TARGET_POINTER_SIZE; } } else { - pLoc->m_idxStack = TransitionBlock::GetStackArgumentIndexFromOffset(argOffset); - pLoc->m_cStack = cSlots; + pLoc->m_byteStackIndex = TransitionBlock::GetStackArgumentByteIndexFromOffset(argOffset); + pLoc->m_byteStackSize = StackElemSize(byteArgSize); } } #endif // TARGET_ARM @@ -661,16 +670,18 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE pLoc->Init(); + if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) { - // Dividing by 16 as size of each register in FloatArgumentRegisters is 16 bytes. - pLoc->m_idxFloatReg = (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()) / 16; + const int floatRegOfsInBytes = argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters(); + _ASSERTE((floatRegOfsInBytes % FLOAT_REGISTER_SIZE) == 0); + pLoc->m_idxFloatReg = floatRegOfsInBytes / FLOAT_REGISTER_SIZE; if (!m_argTypeHandle.IsNull() && m_argTypeHandle.IsHFA()) { CorInfoHFAElemType type = m_argTypeHandle.GetHFAType(); pLoc->setHFAFieldSize(type); - pLoc->m_cFloatReg = GetArgSize()/pLoc->m_hfaFieldSize; + pLoc->m_cFloatReg = GetArgSize() / pLoc->m_hfaFieldSize; } else @@ -680,29 +691,31 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE return; } - int cSlots = (GetArgSize() + 7)/ 8; + unsigned byteArgSize = GetArgSize(); + // Question: why do not arm and x86 have similar checks? // Composites greater than 16 bytes are passed by reference - if (GetArgType() == ELEMENT_TYPE_VALUETYPE && GetArgSize() > ENREGISTERED_PARAMTYPE_MAXSIZE) + if ((GetArgType() == ELEMENT_TYPE_VALUETYPE) && (byteArgSize > ENREGISTERED_PARAMTYPE_MAXSIZE)) { - cSlots = 1; + byteArgSize = TARGET_POINTER_SIZE; } -#ifdef TARGET_ARM64 + // Sanity check to make sure no caller is trying to get an ArgLocDesc that // describes the return buffer reg field that's in the TransitionBlock. _ASSERTE(argOffset != TransitionBlock::GetOffsetOfRetBuffArgReg()); -#endif if (!TransitionBlock::IsStackArgumentOffset(argOffset)) { pLoc->m_idxGenReg = TransitionBlock::GetArgumentIndexFromOffset(argOffset); - pLoc->m_cGenReg = cSlots; + pLoc->m_cGenReg = ALIGN_UP(byteArgSize, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE;; } else { - pLoc->m_idxStack = TransitionBlock::GetStackArgumentIndexFromOffset(argOffset); - pLoc->m_cStack = cSlots; + pLoc->m_byteStackIndex = TransitionBlock::GetStackArgumentByteIndexFromOffset(argOffset); + const bool isValueType = (m_argType == ELEMENT_TYPE_VALUETYPE); + const bool isFloatHfa = (isValueType && !m_argTypeHandle.IsNull() && m_argTypeHandle.IsHFA()); + pLoc->m_byteStackSize = StackElemSize(byteArgSize, isValueType, isFloatHfa); } } #endif // TARGET_ARM64 @@ -734,8 +747,9 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE #if defined(UNIX_AMD64_ABI) if (TransitionBlock::IsFloatArgumentRegisterOffset(argOffset)) { - // Dividing by 16 as size of each register in FloatArgumentRegisters is 16 bytes. - pLoc->m_idxFloatReg = (argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters()) / 16; + const int floatRegOfsInBytes = argOffset - TransitionBlock::GetOffsetOfFloatArgumentRegisters(); + _ASSERTE((floatRegOfsInBytes % FLOAT_REGISTER_SIZE) == 0); + pLoc->m_idxFloatReg = floatRegOfsInBytes / FLOAT_REGISTER_SIZE; pLoc->m_cFloatReg = 1; } else @@ -758,13 +772,13 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE } else { - pLoc->m_idxStack = TransitionBlock::GetStackArgumentIndexFromOffset(argOffset); - int argOnStackSize; + pLoc->m_byteStackIndex = TransitionBlock::GetStackArgumentByteIndexFromOffset(argOffset); + int argSizeInBytes; if (IsArgPassedByRef()) - argOnStackSize = STACK_ELEM_SIZE; + argSizeInBytes = TARGET_POINTER_SIZE; else - argOnStackSize = GetArgSize(); - pLoc->m_cStack = (argOnStackSize + STACK_ELEM_SIZE - 1) / STACK_ELEM_SIZE; + argSizeInBytes = GetArgSize(); + pLoc->m_byteStackSize = StackElemSize(argSizeInBytes); } } #endif // TARGET_AMD64 @@ -784,36 +798,29 @@ class ArgIteratorTemplate : public ARGITERATOR_BASE bool m_hasArgLocDescForStructInRegs; #endif // (TARGET_AMD64 && UNIX_AMD64_ABI) || TARGET_ARM64 + int m_ofsStack; // Current position of the stack iterator, in bytes + #ifdef TARGET_X86 - int m_curOfs; // Current position of the stack iterator int m_numRegistersUsed; #ifdef FEATURE_INTERPRETER bool m_fUnmanagedCallConv; #endif #endif -#ifdef TARGET_AMD64 #ifdef UNIX_AMD64_ABI int m_idxGenReg; // Next general register to be assigned a value - int m_idxStack; // Next stack slot to be assigned a value int m_idxFPReg; // Next floating point register to be assigned a value bool m_fArgInRegisters; // Indicates that the current argument is stored in registers -#else - int m_curOfs; // Current position of the stack iterator -#endif #endif #ifdef TARGET_ARM int m_idxGenReg; // Next general register to be assigned a value - int m_idxStack; // Next stack slot to be assigned a value - WORD m_wFPRegs; // Bitmask of available floating point argument registers (s0-s15/d0-d7) bool m_fRequires64BitAlignment; // Cached info about the current arg #endif #ifdef TARGET_ARM64 int m_idxGenReg; // Next general register to be assigned a value - int m_idxStack; // Next stack slot to be assigned a value int m_idxFPReg; // Next FP register to be assigned a value #endif @@ -1023,7 +1030,7 @@ int ArgIteratorTemplate::GetNextOffset() case IMAGE_CEE_CS_CALLCONV_C: case IMAGE_CEE_CS_CALLCONV_STDCALL: m_numRegistersUsed = NUM_ARGUMENT_REGISTERS; - m_curOfs = TransitionBlock::GetOffsetOfArgs() + numRegistersUsed * sizeof(void *); + m_ofsStack = TransitionBlock::GetOffsetOfArgs() + numRegistersUsed * sizeof(void *); m_fUnmanagedCallConv = true; break; @@ -1034,30 +1041,30 @@ int ArgIteratorTemplate::GetNextOffset() default: m_fUnmanagedCallConv = false; m_numRegistersUsed = numRegistersUsed; - m_curOfs = TransitionBlock::GetOffsetOfArgs() + SizeOfArgStack(); + m_ofsStack = TransitionBlock::GetOffsetOfArgs() + SizeOfArgStack(); break; } #else m_numRegistersUsed = numRegistersUsed; - m_curOfs = TransitionBlock::GetOffsetOfArgs() + SizeOfArgStack(); + m_ofsStack = TransitionBlock::GetOffsetOfArgs() + SizeOfArgStack(); #endif #elif defined(TARGET_AMD64) #ifdef UNIX_AMD64_ABI m_idxGenReg = numRegistersUsed; - m_idxStack = 0; + m_ofsStack = 0; m_idxFPReg = 0; #else - m_curOfs = TransitionBlock::GetOffsetOfArgs() + numRegistersUsed * sizeof(void *); + m_ofsStack = TransitionBlock::GetOffsetOfArgs() + numRegistersUsed * sizeof(void *); #endif #elif defined(TARGET_ARM) m_idxGenReg = numRegistersUsed; - m_idxStack = 0; + m_ofsStack = 0; m_wFPRegs = 0; #elif defined(TARGET_ARM64) m_idxGenReg = numRegistersUsed; - m_idxStack = 0; + m_ofsStack = 0; m_idxFPReg = 0; #else @@ -1090,8 +1097,8 @@ int ArgIteratorTemplate::GetNextOffset() #ifdef FEATURE_INTERPRETER if (m_fUnmanagedCallConv) { - int argOfs = m_curOfs; - m_curOfs += StackElemSize(argSize); + int argOfs = m_ofsStack; + m_ofsStack += StackElemSize(argSize); return argOfs; } #endif @@ -1100,9 +1107,9 @@ int ArgIteratorTemplate::GetNextOffset() return TransitionBlock::GetOffsetOfArgumentRegisters() + (NUM_ARGUMENT_REGISTERS - m_numRegistersUsed) * sizeof(void *); } - m_curOfs -= StackElemSize(argSize); - _ASSERTE(m_curOfs >= TransitionBlock::GetOffsetOfArgs()); - return m_curOfs; + m_ofsStack -= StackElemSize(argSize); + _ASSERTE(m_ofsStack >= TransitionBlock::GetOffsetOfArgs()); + return m_ofsStack; #elif defined(TARGET_AMD64) #ifdef UNIX_AMD64_ABI @@ -1195,16 +1202,15 @@ int ArgIteratorTemplate::GetNextOffset() m_fArgInRegisters = false; - int argOfs = TransitionBlock::GetOffsetOfArgs() + m_idxStack * STACK_ELEM_SIZE; + int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; - int cArgSlots = cbArg / STACK_ELEM_SIZE; - m_idxStack += cArgSlots; + m_ofsStack += cbArg; return argOfs; #else // Each argument takes exactly one slot on AMD64 on Windows - int argOfs = m_curOfs; - m_curOfs += sizeof(void *); + int argOfs = m_ofsStack; + m_ofsStack += sizeof(void *); return argOfs; #endif #elif defined(TARGET_ARM) @@ -1266,7 +1272,7 @@ int ArgIteratorTemplate::GetNextOffset() m_fRequires64BitAlignment = fRequiresAlign64Bit; int cbArg = StackElemSize(argSize); - int cArgSlots = cbArg / 4; + _ASSERTE((cbArg % TARGET_POINTER_SIZE) == 0); // Ignore floating point argument placement in registers if we're dealing with a vararg function (the ABI // specifies this so that vararg processing on the callee side is simplified). @@ -1313,13 +1319,15 @@ int ArgIteratorTemplate::GetNextOffset() // Doubles or HFAs containing doubles need the stack aligned appropriately. if (fRequiresAlign64Bit) - m_idxStack = (int)ALIGN_UP(m_idxStack, 2); + { + m_ofsStack = (int)ALIGN_UP(m_ofsStack, TARGET_POINTER_SIZE * 2); + } // Indicate the stack location of the argument to the caller. - int argOfs = TransitionBlock::GetOffsetOfArgs() + m_idxStack * 4; + int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; // Record the stack usage. - m_idxStack += cArgSlots; + m_ofsStack += cbArg; return argOfs; } @@ -1341,10 +1349,10 @@ int ArgIteratorTemplate::GetNextOffset() int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 4; int cRemainingRegs = 4 - m_idxGenReg; - if (cArgSlots <= cRemainingRegs) + if (cbArg <= cRemainingRegs * TARGET_POINTER_SIZE) { // Mark the registers just allocated as used. - m_idxGenReg += cArgSlots; + m_idxGenReg += ALIGN_UP(cbArg, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; return argOfs; } @@ -1355,9 +1363,9 @@ int ArgIteratorTemplate::GetNextOffset() m_idxGenReg = 4; - if (m_idxStack == 0) + if (m_ofsStack == 0) { - m_idxStack += cArgSlots - cRemainingRegs; + m_ofsStack += cbArg - cRemainingRegs * TARGET_POINTER_SIZE; return argOfs; } } @@ -1366,13 +1374,13 @@ int ArgIteratorTemplate::GetNextOffset() { // The argument requires 64-bit alignment. If it is going to be passed on the stack, align // the next stack slot. See step C.6 in the algorithm in the ABI spec. - m_idxStack = (int)ALIGN_UP(m_idxStack, 2); + m_ofsStack = (int)ALIGN_UP(m_ofsStack, TARGET_POINTER_SIZE * 2); } - int argOfs = TransitionBlock::GetOffsetOfArgs() + m_idxStack * 4; + int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; // Advance the stack pointer over the argument just placed. - m_idxStack += cArgSlots; + m_ofsStack += cbArg; return argOfs; #elif defined(TARGET_ARM64) @@ -1428,10 +1436,8 @@ int ArgIteratorTemplate::GetNextOffset() default: break; } - - int cbArg = StackElemSize(argSize); - int cArgSlots = cbArg / STACK_ELEM_SIZE; - + const bool isValueType = (argType == ELEMENT_TYPE_VALUETYPE); + const int cbArg = StackElemSize(argSize, isValueType, thValueType.IsFloatHfa()); if (cFPRegs>0 && !this->IsVarArg()) { if (cFPRegs + m_idxFPReg <= 8) @@ -1448,13 +1454,17 @@ int ArgIteratorTemplate::GetNextOffset() } else { +#if !defined(OSX_ARM64_ABI) + _ASSERTE((cbArg% TARGET_POINTER_SIZE) == 0); +#endif + const int regSlots = ALIGN_UP(cbArg, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; // Only x0-x7 are valid argument registers (x8 is always the return buffer) - if (m_idxGenReg + cArgSlots <= 8) + if (m_idxGenReg + regSlots <= 8) { // The entirety of the arg fits in the register slots. int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; - m_idxGenReg += cArgSlots; + m_idxGenReg += regSlots; return argOfs; } else @@ -1467,9 +1477,9 @@ int ArgIteratorTemplate::GetNextOffset() // into x0-x7, and any remaining stack arguments are placed normally. int argOfs = TransitionBlock::GetOffsetOfArgumentRegisters() + m_idxGenReg * 8; - // Increase m_idxStack to account for the space used for the remainder of the arg after - // register slots are filled. - m_idxStack += (m_idxGenReg + cArgSlots - 8); + // Increase m_ofsStack to account for the space used for the remainder of the arg after + // registers are filled. + m_ofsStack += cbArg + (m_idxGenReg - 8) * TARGET_POINTER_SIZE; // We used up the remaining reg slots. m_idxGenReg = 8; @@ -1485,8 +1495,8 @@ int ArgIteratorTemplate::GetNextOffset() } } - int argOfs = TransitionBlock::GetOffsetOfArgs() + m_idxStack * 8; - m_idxStack += cArgSlots; + int argOfs = TransitionBlock::GetOffsetOfArgs() + m_ofsStack; + m_ofsStack += cbArg; return argOfs; #else PORTABILITY_ASSERT("ArgIteratorTemplate::GetNextOffset"); @@ -1731,13 +1741,17 @@ void ArgIteratorTemplate::ForceSigWalk() #else // UNIX_AMD64_ABI // All stack arguments take just one stack slot on AMD64 because of arguments bigger // than a stack slot are passed by reference. - stackElemSize = STACK_ELEM_SIZE; + stackElemSize = TARGET_POINTER_SIZE; #endif // UNIX_AMD64_ABI #else // TARGET_AMD64 - stackElemSize = StackElemSize(GetArgSize()); + + TypeHandle thValueType; + const CorElementType argType = GetArgType(&thValueType); + const bool isValueType = (argType == ELEMENT_TYPE_VALUETYPE); + stackElemSize = StackElemSize(GetArgSize(), isValueType, thValueType.IsFloatHfa()); #if defined(ENREGISTERED_PARAMTYPE_MAXSIZE) if (IsArgPassedByRef()) - stackElemSize = STACK_ELEM_SIZE; + stackElemSize = TARGET_POINTER_SIZE; #endif #endif // TARGET_AMD64 @@ -1771,6 +1785,9 @@ void ArgIteratorTemplate::ForceSigWalk() #endif // TARGET_X86 + // arg stack size is rounded to the pointer size on all platforms. + nSizeOfArgStack = (int)ALIGN_UP(nSizeOfArgStack, TARGET_POINTER_SIZE); + // Cache the result m_nSizeOfArgStack = nSizeOfArgStack; m_dwFlags |= SIZE_OF_ARG_STACK_COMPUTED; diff --git a/src/coreclr/vm/class.cpp b/src/coreclr/vm/class.cpp index 4ce15e98d36a7..f39e1f23e9651 100644 --- a/src/coreclr/vm/class.cpp +++ b/src/coreclr/vm/class.cpp @@ -1169,21 +1169,11 @@ void ClassLoader::ValidateMethodsWithCovariantReturnTypes(MethodTable* pMT) if (!pMD->RequiresCovariantReturnTypeChecking() && !pParentMD->RequiresCovariantReturnTypeChecking()) continue; - Instantiation parentClassInst = pParentMD->GetClassInstantiation(); - if (ClassLoader::IsTypicalSharedInstantiation(parentClassInst)) - { - parentClassInst = pParentMT->GetInstantiation(); - } - SigTypeContext context1(parentClassInst, pMD->GetMethodInstantiation()); + SigTypeContext context1(pParentMT->GetInstantiation(), pMD->GetMethodInstantiation()); MetaSig methodSig1(pParentMD); TypeHandle hType1 = methodSig1.GetReturnProps().GetTypeHandleThrowing(pParentMD->GetModule(), &context1, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS); - Instantiation classInst = pMD->GetClassInstantiation(); - if (ClassLoader::IsTypicalSharedInstantiation(classInst)) - { - classInst = pMT->GetInstantiation(); - } - SigTypeContext context2(classInst, pMD->GetMethodInstantiation()); + SigTypeContext context2(pMT->GetInstantiation(), pMD->GetMethodInstantiation()); MetaSig methodSig2(pMD); TypeHandle hType2 = methodSig2.GetReturnProps().GetTypeHandleThrowing(pMD->GetModule(), &context2, ClassLoader::LoadTypesFlag::LoadTypes, CLASS_LOAD_EXACTPARENTS); diff --git a/src/coreclr/vm/clrtocomcall.cpp b/src/coreclr/vm/clrtocomcall.cpp index 914d28f7c5983..26ae406dab8f7 100644 --- a/src/coreclr/vm/clrtocomcall.cpp +++ b/src/coreclr/vm/clrtocomcall.cpp @@ -165,7 +165,7 @@ MethodDesc* ComPlusCall::GetILStubMethodDesc(MethodDesc* pMD, DWORD dwStubFlags) &sigDesc, (CorNativeLinkType)0, (CorNativeLinkFlags)0, - (CorPinvokeMap)0, + MetaSig::GetDefaultUnmanagedCallingConvention(), dwStubFlags); } diff --git a/src/coreclr/vm/clrvarargs.cpp b/src/coreclr/vm/clrvarargs.cpp index 303220b4464a6..37c30739aedf5 100644 --- a/src/coreclr/vm/clrvarargs.cpp +++ b/src/coreclr/vm/clrvarargs.cpp @@ -76,7 +76,9 @@ VARARGS::MarshalToUnmanagedVaList( case ELEMENT_TYPE_U: case ELEMENT_TYPE_PTR: { - DWORD cbSize = StackElemSize(CorTypeInfo::Size(elemType)); + const bool isValueType = false; + const bool isFloatHfa = false; + DWORD cbSize = StackElemSize(CorTypeInfo::Size(elemType), isValueType, isFloatHfa); #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE if (cbSize > ENREGISTERED_PARAMTYPE_MAXSIZE) diff --git a/src/coreclr/vm/comdelegate.cpp b/src/coreclr/vm/comdelegate.cpp index d484cb134644c..17235f9a164c8 100644 --- a/src/coreclr/vm/comdelegate.cpp +++ b/src/coreclr/vm/comdelegate.cpp @@ -75,8 +75,8 @@ class ShuffleIterator int m_currentGenRegIndex; // Current floating point register index (relative to the ArgLocDesc::m_idxFloatReg) int m_currentFloatRegIndex; - // Current stack slot index (relative to the ArgLocDesc::m_idxStack) - int m_currentStackSlotIndex; + // Current byte stack index (relative to the ArgLocDesc::m_byteStackIndex) + int m_currentByteStackIndex; #if defined(UNIX_AMD64_ABI) // Get next shuffle offset for struct passed in registers. There has to be at least one offset left. @@ -134,7 +134,7 @@ class ShuffleIterator #endif m_currentGenRegIndex(0), m_currentFloatRegIndex(0), - m_currentStackSlotIndex(0) + m_currentByteStackIndex(0) { } @@ -143,11 +143,13 @@ class ShuffleIterator { return (m_currentGenRegIndex < m_argLocDesc->m_cGenReg) || (m_currentFloatRegIndex < m_argLocDesc->m_cFloatReg) || - (m_currentStackSlotIndex < m_argLocDesc->m_cStack); + (m_currentByteStackIndex < m_argLocDesc->m_byteStackSize); } // Get next offset to shuffle. There has to be at least one offset left. - UINT16 GetNextOfs() + // For register arguments it returns regNum | ShuffleEntry::REGMASK | ShuffleEntry::FPREGMASK. + // For stack arguments it returns stack offset in bytes with negative sign. + int GetNextOfs() { int index; @@ -157,7 +159,9 @@ class ShuffleIterator EEClass* eeClass = m_argLocDesc->m_eeClass; if (m_argLocDesc->m_eeClass != 0) { - return GetNextOfsInStruct(); + index = GetNextOfsInStruct(); + _ASSERT((index & ShuffleEntry::REGMASK) != 0); + return index; } #endif // UNIX_AMD64_ABI @@ -167,7 +171,7 @@ class ShuffleIterator index = m_argLocDesc->m_idxFloatReg + m_currentFloatRegIndex; m_currentFloatRegIndex++; - return (UINT16)index | ShuffleEntry::REGMASK | ShuffleEntry::FPREGMASK; + return index | ShuffleEntry::REGMASK | ShuffleEntry::FPREGMASK; } // Shuffle any registers first (the order matters since otherwise we could end up shuffling a stack slot @@ -177,15 +181,16 @@ class ShuffleIterator index = m_argLocDesc->m_idxGenReg + m_currentGenRegIndex; m_currentGenRegIndex++; - return (UINT16)index | ShuffleEntry::REGMASK; + return index | ShuffleEntry::REGMASK; } // If we get here we must have at least one stack slot left to shuffle (this method should only be called // when AnythingToShuffle(pArg) == true). - if (m_currentStackSlotIndex < m_argLocDesc->m_cStack) + if (m_currentByteStackIndex < m_argLocDesc->m_byteStackSize) { - index = m_argLocDesc->m_idxStack + m_currentStackSlotIndex; - m_currentStackSlotIndex++; + const unsigned byteIndex = m_argLocDesc->m_byteStackIndex + m_currentByteStackIndex; + index = byteIndex / TARGET_POINTER_SIZE; + m_currentByteStackIndex += TARGET_POINTER_SIZE; // Delegates cannot handle overly large argument stacks due to shuffle entry encoding limitations. if (index >= ShuffleEntry::REGMASK) @@ -193,7 +198,7 @@ class ShuffleIterator COMPlusThrow(kNotSupportedException); } - return (UINT16)index; + return -(int)byteIndex; } // There are no more offsets to get, the caller should not have called us @@ -262,13 +267,39 @@ BOOL AddNextShuffleEntryToArray(ArgLocDesc sArgSrc, ArgLocDesc sArgDst, SArrayAppend(entry); } } @@ -605,13 +637,13 @@ VOID GenerateShuffleArray(MethodDesc* pInvoke, MethodDesc *pTargetMeth, SArrayAppend(entry); - cbSize -= STACK_ELEM_SIZE; + cbSize -= TARGET_POINTER_SIZE; } while (cbSize > 0); } @@ -1132,29 +1164,6 @@ void COMDelegate::BindToMethod(DELEGATEREF *pRefThis, GCPROTECT_END(); } -#if defined(TARGET_X86) -// Marshals a managed method to an unmanaged callback. -PCODE COMDelegate::ConvertToUnmanagedCallback(MethodDesc* pMD) -{ - CONTRACTL - { - THROWS; - GC_TRIGGERS; - PRECONDITION(pMD != NULL); - PRECONDITION(pMD->HasUnmanagedCallersOnlyAttribute()); - INJECT_FAULT(COMPlusThrowOM()); - } - CONTRACTL_END; - - // Get UMEntryThunk from the thunk cache. - UMEntryThunk *pUMEntryThunk = pMD->GetLoaderAllocator()->GetUMEntryThunkCache()->GetUMEntryThunk(pMD); - - PCODE pCode = (PCODE)pUMEntryThunk->GetCode(); - _ASSERTE(pCode != NULL); - return pCode; -} -#endif // defined(TARGET_X86) - // Marshals a delegate to a unmanaged callback. LPVOID COMDelegate::ConvertToCallback(OBJECTREF pDelegateObj) { @@ -1367,31 +1376,6 @@ OBJECTREF COMDelegate::ConvertToDelegate(LPVOID pCallback, MethodTable* pMT) delObj->SetInvocationCount(DELEGATE_MARKER_UNMANAGEDFPTR); } -#if defined(TARGET_X86) - GCPROTECT_BEGIN(delObj); - - Stub *pInterceptStub = NULL; - - { - GCX_PREEMP(); - - MethodDesc *pStubMD = pClass->m_pForwardStubMD; - _ASSERTE(pStubMD != NULL && pStubMD->IsILStub()); - - } - - if (pInterceptStub != NULL) - { - // install the outer-most stub to sync block - SyncBlock *pSyncBlock = delObj->GetSyncBlock(); - - InteropSyncBlockInfo *pInteropInfo = pSyncBlock->GetInteropInfo(); - VERIFY(pInteropInfo->SetInterceptStub(pInterceptStub)); - } - - GCPROTECT_END(); -#endif // TARGET_X86 - return delObj; } diff --git a/src/coreclr/vm/comdelegate.h b/src/coreclr/vm/comdelegate.h index 40e80246de095..b9ddb37d0b8b1 100644 --- a/src/coreclr/vm/comdelegate.h +++ b/src/coreclr/vm/comdelegate.h @@ -83,12 +83,6 @@ class COMDelegate // Marshals a delegate to a unmanaged callback. static LPVOID ConvertToCallback(OBJECTREF pDelegate); -#if defined(TARGET_X86) - // Marshals a managed method to an unmanaged callback. - // This is only used on x86. See usage for further details. - static PCODE ConvertToUnmanagedCallback(MethodDesc* pMD); -#endif // defined(TARGET_X86) - // Marshals an unmanaged callback to Delegate static OBJECTREF ConvertToDelegate(LPVOID pCallback, MethodTable* pMT); diff --git a/src/coreclr/vm/comtoclrcall.cpp b/src/coreclr/vm/comtoclrcall.cpp index 6a68c2c2009d1..0a5be3cb5f8a2 100644 --- a/src/coreclr/vm/comtoclrcall.cpp +++ b/src/coreclr/vm/comtoclrcall.cpp @@ -226,7 +226,7 @@ inline static void InvokeStub(ComCallMethodDesc *pCMD, PCODE pManagedTarget, OBJ INT_PTR dangerousThis; *(OBJECTREF *)&dangerousThis = orThis; - DWORD dwStackSlots = pCMD->GetNumStackBytes() / STACK_ELEM_SIZE; + DWORD dwStackSlots = pCMD->GetNumStackBytes() / TARGET_POINTER_SIZE; // Managed code is generally "THROWS" and we have no exception handler here that the contract system can // see. We ensure that we don't get exceptions here by generating a try/catch in the IL stub that covers @@ -827,7 +827,7 @@ void ComCallMethodDesc::InitRuntimeNativeInfo(MethodDesc *pStubMD) NewArrayHolder pwStubStackSlotOffsets; UINT16 *pOutputStack = NULL; - UINT16 wStubStackSlotCount = static_cast(dwArgStack) / STACK_ELEM_SIZE; + UINT16 wStubStackSlotCount = static_cast(dwArgStack) / TARGET_POINTER_SIZE; if (wStubStackSlotCount > 0) { pwStubStackSlotOffsets = new UINT16[wStubStackSlotCount]; @@ -843,15 +843,15 @@ void ComCallMethodDesc::InitRuntimeNativeInfo(MethodDesc *pStubMD) if (!pStubMD->IsStatic()) { numRegistersUsed++; - wInputStack += STACK_ELEM_SIZE; + wInputStack += TARGET_POINTER_SIZE; } // process the return buffer parameter if (argit.HasRetBuffArg()) { numRegistersUsed++; - wSourceSlotEDX = wInputStack / STACK_ELEM_SIZE; - wInputStack += STACK_ELEM_SIZE; + wSourceSlotEDX = wInputStack / TARGET_POINTER_SIZE; + wInputStack += TARGET_POINTER_SIZE; } // process ordinary parameters @@ -864,17 +864,18 @@ void ComCallMethodDesc::InitRuntimeNativeInfo(MethodDesc *pStubMD) if (ArgIterator::IsArgumentInRegister(&numRegistersUsed, type, thValueType)) { - wSourceSlotEDX = wInputStack / STACK_ELEM_SIZE; - wInputStack += STACK_ELEM_SIZE; + wSourceSlotEDX = wInputStack / TARGET_POINTER_SIZE; + wInputStack += TARGET_POINTER_SIZE; } else { // we may need more stack slots for larger parameters - pOutputStack -= StackElemSize(cbSize) / STACK_ELEM_SIZE; - for (UINT slot = 0; slot < (StackElemSize(cbSize) / STACK_ELEM_SIZE); slot++) + UINT slotsCount = StackElemSize(cbSize) / TARGET_POINTER_SIZE; + pOutputStack -= slotsCount; + for (UINT slot = 0; slot < slotsCount; slot++) { pOutputStack[slot] = wInputStack; - wInputStack += STACK_ELEM_SIZE; + wInputStack += TARGET_POINTER_SIZE; } } } @@ -1461,7 +1462,7 @@ MethodDesc* ComCall::GetILStubMethodDesc(MethodDesc *pCallMD, DWORD dwStubFlags) return NDirect::CreateCLRToNativeILStub(&sigDesc, (CorNativeLinkType)0, (CorNativeLinkFlags)0, - (CorPinvokeMap)0, + MetaSig::GetDefaultUnmanagedCallingConvention(), dwStubFlags); } diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index 5f14adaeda712..d8004a37194be 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -1537,19 +1537,6 @@ FCIMPL2_IV(INT64,COMInterlocked::Exchange64, INT64 *location, INT64 value) } FCIMPLEND -FCIMPL2(LPVOID,COMInterlocked::ExchangePointer, LPVOID *location, LPVOID value) -{ - FCALL_CONTRACT; - - if( NULL == location) { - FCThrow(kNullReferenceException); - } - - FCUnique(0x15); - return FastInterlockExchangePointer(location, value); -} -FCIMPLEND - FCIMPL3(INT32, COMInterlocked::CompareExchange, INT32* location, INT32 value, INT32 comparand) { FCALL_CONTRACT; @@ -1574,19 +1561,6 @@ FCIMPL3_IVV(INT64, COMInterlocked::CompareExchange64, INT64* location, INT64 val } FCIMPLEND -FCIMPL3(LPVOID,COMInterlocked::CompareExchangePointer, LPVOID *location, LPVOID value, LPVOID comparand) -{ - FCALL_CONTRACT; - - if( NULL == location) { - FCThrow(kNullReferenceException); - } - - FCUnique(0x59); - return FastInterlockCompareExchangePointer(location, value, comparand); -} -FCIMPLEND - FCIMPL2_IV(float,COMInterlocked::ExchangeFloat, float *location, float value) { FCALL_CONTRACT; diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index 2d1b2df9bfc2a..f59bbe7f66c11 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -215,10 +215,8 @@ class COMInterlocked public: static FCDECL2(INT32, Exchange, INT32 *location, INT32 value); static FCDECL2_IV(INT64, Exchange64, INT64 *location, INT64 value); - static FCDECL2(LPVOID, ExchangePointer, LPVOID* location, LPVOID value); static FCDECL3(INT32, CompareExchange, INT32* location, INT32 value, INT32 comparand); static FCDECL3_IVV(INT64, CompareExchange64, INT64* location, INT64 value, INT64 comparand); - static FCDECL3(LPVOID, CompareExchangePointer, LPVOID* location, LPVOID value, LPVOID comparand); static FCDECL2_IV(float, ExchangeFloat, float *location, float value); static FCDECL2_IV(double, ExchangeDouble, double *location, double value); static FCDECL3_IVV(float, CompareExchangeFloat, float *location, float value, float comparand); diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index 33aa48cf5c104..920502d95aad6 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -779,11 +779,7 @@ HRESULT CorHost2::CreateDelegate( if (pMD->HasUnmanagedCallersOnlyAttribute()) { -#ifdef TARGET_X86 - *fnPtr = (INT_PTR)COMDelegate::ConvertToUnmanagedCallback(pMD); -#else *fnPtr = pMD->GetMultiCallableAddrOfCode(); -#endif } else { diff --git a/src/coreclr/vm/dllimport.cpp b/src/coreclr/vm/dllimport.cpp index d92ac44b358c3..90199d48912bc 100644 --- a/src/coreclr/vm/dllimport.cpp +++ b/src/coreclr/vm/dllimport.cpp @@ -1340,7 +1340,7 @@ class PInvoke_ILStubState : public ILStubState public: PInvoke_ILStubState(Module* pStubModule, const Signature &signature, SigTypeContext *pTypeContext, DWORD dwStubFlags, - CorPinvokeMap unmgdCallConv, int iLCIDParamIdx, MethodDesc* pTargetMD) + CorInfoCallConvExtension unmgdCallConv, int iLCIDParamIdx, MethodDesc* pTargetMD) : ILStubState( pStubModule, signature, @@ -1350,18 +1350,7 @@ class PInvoke_ILStubState : public ILStubState pTargetMD) { STANDARD_VM_CONTRACT; - -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - // x86 with non-IL stubs manually handles calling conventions - // for reverse P/Invokes with the x86 stub linker. - // Don't use the JIT calling convention support on reverse P/Invokes. - if (SF_IsForwardStub(dwStubFlags)) - { - m_slIL.SetCallingConvention(unmgdCallConv, SF_IsVarArgStub(dwStubFlags)); - } -#else m_slIL.SetCallingConvention(unmgdCallConv, SF_IsVarArgStub(dwStubFlags)); -#endif } private: @@ -1426,7 +1415,7 @@ class CLRToCOM_ILStubState : public ILStubState if (SF_IsForwardStub(dwStubFlags)) { - m_slIL.SetCallingConvention(pmCallConvStdcall, SF_IsVarArgStub(dwStubFlags)); + m_slIL.SetCallingConvention(CorInfoCallConvExtension::Stdcall, SF_IsVarArgStub(dwStubFlags)); } } @@ -1623,40 +1612,22 @@ NDirectStubLinker::NDirectStubLinker( #endif // FEATURE_COMINTEROP } -void NDirectStubLinker::SetCallingConvention(CorPinvokeMap unmngCallConv, BOOL fIsVarArg) +void NDirectStubLinker::SetCallingConvention(CorInfoCallConvExtension unmngCallConv, BOOL fIsVarArg) { LIMITED_METHOD_CONTRACT; - ULONG uNativeCallingConv = 0; #if !defined(TARGET_X86) if (fIsVarArg) { // The JIT has to use a different calling convention for unmanaged vararg targets on 64-bit and ARM: // any float values must be duplicated in the corresponding general-purpose registers. - uNativeCallingConv = IMAGE_CEE_CS_CALLCONV_NATIVEVARARG; + SetStubTargetCallingConv(IMAGE_CEE_CS_CALLCONV_NATIVEVARARG); } else #endif // !TARGET_X86 { - switch (unmngCallConv) - { - case pmCallConvCdecl: - uNativeCallingConv = IMAGE_CEE_CS_CALLCONV_C; - break; - case pmCallConvStdcall: - uNativeCallingConv = IMAGE_CEE_CS_CALLCONV_STDCALL; - break; - case pmCallConvThiscall: - uNativeCallingConv = IMAGE_CEE_CS_CALLCONV_THISCALL; - break; - default: - _ASSERTE(!"Invalid calling convention."); - uNativeCallingConv = IMAGE_CEE_CS_CALLCONV_STDCALL; - break; - } + SetStubTargetCallingConv(unmngCallConv); } - - SetStubTargetCallingConv((CorCallingConvention)uNativeCallingConv); } void NDirectStubLinker::EmitSetArgMarshalIndex(ILCodeStream* pcsEmit, UINT uArgIdx) @@ -2522,6 +2493,36 @@ class DispatchStubState : public StubState // For CLR-to-COM late-bound/eventing #endif // FEATURE_COMINTEROP +namespace +{ + // Use CorInfoCallConvExtension::Managed as a sentinel represent a user-provided WinApi calling convention. + constexpr CorInfoCallConvExtension CallConvWinApiSentinel = CorInfoCallConvExtension::Managed; + + // Returns the unmanaged calling convention for callConv or CallConvWinApiSentinel + // if the calling convention is not provided or WinApi. + CorInfoCallConvExtension GetCallConvValueForPInvokeCallConv(CorPinvokeMap callConv) + { + LIMITED_METHOD_CONTRACT; + + switch (callConv) + { + case 0: + case pmCallConvWinapi: + return CallConvWinApiSentinel; + case pmCallConvCdecl: + return CorInfoCallConvExtension::C; + case pmCallConvStdcall: + return CorInfoCallConvExtension::Stdcall; + case pmCallConvThiscall: + return CorInfoCallConvExtension::Thiscall; + case pmCallConvFastcall: + return CorInfoCallConvExtension::Fastcall; + default: + _ASSERTE_MSG(false, "Invalid PInvoke callconv."); + return CallConvWinApiSentinel; + } + } +} void PInvokeStaticSigInfo::PreInit(Module* pModule, MethodTable * pMT) { @@ -2536,7 +2537,7 @@ void PInvokeStaticSigInfo::PreInit(Module* pModule, MethodTable * pMT) // initialize data members m_wFlags = 0; m_pModule = pModule; - m_callConv = (CorPinvokeMap)0; + m_callConv = CallConvWinApiSentinel; SetBestFitMapping (TRUE); SetThrowOnUnmappableChar (FALSE); SetLinkFlags (nlfNone); @@ -2681,7 +2682,7 @@ PInvokeStaticSigInfo::PInvokeStaticSigInfo(MethodDesc* pMD, ThrowOnError throwOn if (hr != S_OK) SetError(IDS_EE_NDIRECT_BADNATL); - InitCallConv(callConv, pMD->IsVarArg()); + InitCallConv(GetCallConvValueForPInvokeCallConv(callConv), pMD->IsVarArg()); if (throwOnError) ReportErrors(); @@ -2701,7 +2702,7 @@ PInvokeStaticSigInfo::PInvokeStaticSigInfo( PreInit(pModule, NULL); m_sig = sig; SetIsStatic (!(MetaSig::GetCallingConvention(pModule, sig) & IMAGE_CEE_CS_CALLCONV_HASTHIS)); - InitCallConv((CorPinvokeMap)0, FALSE); + InitCallConv(CorInfoCallConvExtension::Managed, FALSE); ReportErrors(); } @@ -2741,7 +2742,7 @@ void PInvokeStaticSigInfo::DllImportInit(MethodDesc* pMD, LPCUTF8 *ppLibName, LP BestGuessNDirectDefaults(pMD); #endif - InitCallConv((CorPinvokeMap)0, pMD->IsVarArg()); + InitCallConv(CorInfoCallConvExtension::Managed, pMD->IsVarArg()); return; } @@ -2760,7 +2761,7 @@ void PInvokeStaticSigInfo::DllImportInit(MethodDesc* pMD, LPCUTF8 *ppLibName, LP } // m_callConv - InitCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask), pMD->IsVarArg()); + InitCallConv(GetCallConvValueForPInvokeCallConv((CorPinvokeMap)(mappingFlags & pmCallConvMask)), pMD->IsVarArg()); // m_bestFit CorPinvokeMap bestFitMask = (CorPinvokeMap)(mappingFlags & pmBestFitMask); @@ -2949,97 +2950,47 @@ void PInvokeStaticSigInfo::BestGuessNDirectDefaults(MethodDesc* pMD) #endif // !CROSSGEN_COMPILE -inline CorPinvokeMap GetDefaultCallConv(BOOL bIsVarArg) -{ -#ifdef TARGET_UNIX - return pmCallConvCdecl; -#else // TARGET_UNIX - return bIsVarArg ? pmCallConvCdecl : pmCallConvStdcall; -#endif // !TARGET_UNIX -} - -namespace +CorInfoCallConvExtension GetDefaultCallConv(BOOL bIsVarArg) { - bool TryConvertCallConvValueToPInvokeCallConv(_In_ BYTE callConv, _Out_ CorPinvokeMap *pPinvokeMapOut) - { - LIMITED_METHOD_CONTRACT; - - switch (callConv) - { - case IMAGE_CEE_CS_CALLCONV_C: - *pPinvokeMapOut = pmCallConvCdecl; - return true; - case IMAGE_CEE_CS_CALLCONV_STDCALL: - *pPinvokeMapOut = pmCallConvStdcall; - return true; - case IMAGE_CEE_CS_CALLCONV_THISCALL: - *pPinvokeMapOut = pmCallConvThiscall; - return true; - case IMAGE_CEE_CS_CALLCONV_FASTCALL: - *pPinvokeMapOut = pmCallConvFastcall; - return true; - } - - return false; - } - - HRESULT GetUnmanagedPInvokeCallingConvention( - _In_ Module *pModule, - _In_ PCCOR_SIGNATURE pSig, - _In_ ULONG cSig, - _Out_ CorPinvokeMap *pPinvokeMapOut, - _Out_ UINT *errorResID) - { - STANDARD_VM_CONTRACT; - - CorUnmanagedCallingConvention callConvMaybe; - bool suppressGCTransition; - HRESULT hr = MetaSig::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pModule), pSig, cSig, &callConvMaybe, &suppressGCTransition, errorResID); - if (hr != S_OK) - return hr; - - if (!TryConvertCallConvValueToPInvokeCallConv(callConvMaybe, pPinvokeMapOut)) - return S_FALSE; - - return hr; - } + return bIsVarArg ? CorInfoCallConvExtension::C : MetaSig::GetDefaultUnmanagedCallingConvention(); } -void PInvokeStaticSigInfo::InitCallConv(CorPinvokeMap callConv, BOOL bIsVarArg) +void PInvokeStaticSigInfo::InitCallConv(CorInfoCallConvExtension callConv, BOOL bIsVarArg) { STANDARD_VM_CONTRACT; - // Convert WinAPI methods to either StdCall or CDecl based on if they are varargs or not. - if (callConv == pmCallConvWinapi) - callConv = GetDefaultCallConv(bIsVarArg); - - CorPinvokeMap sigCallConv = (CorPinvokeMap)0; + CorInfoCallConvExtension sigCallConv = CallConvWinApiSentinel; + bool suppressGCTransition; UINT errorResID; - HRESULT hr = GetUnmanagedPInvokeCallingConvention(m_pModule, m_sig.GetRawSig(), m_sig.GetRawSigLen(), &sigCallConv, &errorResID); + HRESULT hr = MetaSig::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(m_pModule), m_sig.GetRawSig(), m_sig.GetRawSigLen(), &sigCallConv, &suppressGCTransition, &errorResID); if (FAILED(hr)) { // Set an error message specific to P/Invokes or UnmanagedFunction for bad format. SetError(hr == COR_E_BADIMAGEFORMAT ? IDS_EE_NDIRECT_BADNATL : errorResID); } + else if (hr == S_FALSE) + { + sigCallConv = CallConvWinApiSentinel; + } - // Do the same WinAPI to StdCall or CDecl for the signature calling convention as well. We need - // to do this before we check to make sure the PInvoke map calling convention and the - // signature calling convention match for compatibility reasons. - if (sigCallConv == pmCallConvWinapi) - sigCallConv = GetDefaultCallConv(bIsVarArg); + // Validate that either no specific calling convention is provided or that the signature calling convention + // matches the DllImport calling convention. + // If no calling convention is provided, then use the default calling convention for the platform. - if (callConv != 0 && sigCallConv != 0 && callConv != sigCallConv) + if (callConv != CallConvWinApiSentinel && sigCallConv != CallConvWinApiSentinel && callConv != sigCallConv) SetError(IDS_EE_NDIRECT_BADNATL_CALLCONV); - if (callConv == 0 && sigCallConv == 0) + if (callConv == CallConvWinApiSentinel && sigCallConv == CallConvWinApiSentinel) m_callConv = GetDefaultCallConv(bIsVarArg); - else if (callConv != 0) + else if (callConv != CallConvWinApiSentinel) m_callConv = callConv; else m_callConv = sigCallConv; - if (bIsVarArg && m_callConv != pmCallConvCdecl) + if (bIsVarArg && m_callConv != CorInfoCallConvExtension::C) SetError(IDS_EE_NDIRECT_BADNATL_VARARGS_CALLCONV); + + _ASSERTE(m_callConv != CallConvWinApiSentinel); } void PInvokeStaticSigInfo::ReportErrors() @@ -3113,7 +3064,7 @@ BOOL NDirect::MarshalingRequired( // point name suffix and affects alignment thunk generation on the Mac). If this method returns // TRUE, the stack size will be set when building the marshaling IL stub. DWORD dwStackSize = 0; - CorPinvokeMap callConv = (CorPinvokeMap)0; + CorInfoCallConvExtension callConv = MetaSig::GetDefaultUnmanagedCallingConvention(); if (pMD != NULL) { @@ -3241,7 +3192,8 @@ BOOL NDirect::MarshalingRequired( #endif if (i > 0) { - dwStackSize += StackElemSize(hndArgType.GetSize()); + const bool isValueType = true; + dwStackSize += StackElemSize(hndArgType.GetSize(), isValueType, hndArgType.IsFloatHfa()); } break; } @@ -3258,7 +3210,13 @@ BOOL NDirect::MarshalingRequired( { if (CorTypeInfo::IsPrimitiveType(type) || type == ELEMENT_TYPE_FNPTR) { - if (i > 0) dwStackSize += StackElemSize(CorTypeInfo::Size(type)); + + if (i > 0) + { + const bool isValueType = false; + const bool isFloatHfa = false; + dwStackSize += StackElemSize(CorTypeInfo::Size(type), isValueType, isFloatHfa); + } } else { @@ -3460,15 +3418,15 @@ static inline UINT GetStackOffsetFromStackSize(UINT stackSize, bool fThisCall) // Note that this function may now throw if it fails to create // a stub. //--------------------------------------------------------- -static void CreateNDirectStubWorker(StubState* pss, - StubSigDesc* pSigDesc, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorPinvokeMap unmgdCallConv, - DWORD dwStubFlags, - MethodDesc *pMD, - mdParamDef* pParamTokenArray, - int iLCIDArg +static void CreateNDirectStubWorker(StubState* pss, + StubSigDesc* pSigDesc, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags, + MethodDesc *pMD, + mdParamDef* pParamTokenArray, + int iLCIDArg ) { CONTRACTL @@ -3493,7 +3451,7 @@ static void CreateNDirectStubWorker(StubState* pss, { _ASSERTE(0 == nlType); _ASSERTE(0 == nlFlags); - _ASSERTE(0 == unmgdCallConv); + _ASSERTE(MetaSig::GetDefaultUnmanagedCallingConvention() == unmgdCallConv); } else { @@ -3512,7 +3470,7 @@ static void CreateNDirectStubWorker(StubState* pss, if (SF_IsVarArgStub(dwStubFlags)) msig.SetTreatAsVarArg(); - bool fThisCall = (unmgdCallConv == pmCallConvThiscall); + bool fThisCall = (unmgdCallConv == CorInfoCallConvExtension::Thiscall); pss->SetLastError(nlFlags & nlfLastError); @@ -4061,24 +4019,24 @@ void NDirect::AddMethodDescChunkWithLockTaken(NDirectStubParameters* pParams, Me // instead of having to generate the IL first before doing the caching. // static void CreateNDirectStubAccessMetadata( - StubSigDesc* pSigDesc, // IN - CorPinvokeMap unmgdCallConv, // IN - DWORD* pdwStubFlags, // IN/OUT - int* piLCIDArg, // OUT - int* pNumArgs // OUT + StubSigDesc* pSigDesc, // IN + CorInfoCallConvExtension unmgdCallConv, // IN + DWORD* pdwStubFlags, // IN/OUT + int* piLCIDArg, // OUT + int* pNumArgs // OUT ) { STANDARD_VM_CONTRACT; if (SF_IsCOMStub(*pdwStubFlags)) { - _ASSERTE(0 == unmgdCallConv); + _ASSERTE(MetaSig::GetDefaultUnmanagedCallingConvention() == unmgdCallConv); } else { - if (unmgdCallConv != pmCallConvStdcall && - unmgdCallConv != pmCallConvCdecl && - unmgdCallConv != pmCallConvThiscall) + if (unmgdCallConv != CorInfoCallConvExtension::Stdcall && + unmgdCallConv != CorInfoCallConvExtension::C && + unmgdCallConv != CorInfoCallConvExtension::Thiscall) { COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV); } @@ -4180,10 +4138,10 @@ void NDirect::PopulateNDirectMethodDesc(NDirectMethodDesc* pNMD, PInvokeStaticSi if (linkflags & nlfNoMangle) ndirectflags |= NDirectMethodDesc::kNativeNoMangle; - CorPinvokeMap callConv = pSigInfo->GetCallConv(); - if (callConv == pmCallConvStdcall) + CorInfoCallConvExtension callConv = pSigInfo->GetCallConv(); + if (callConv == CorInfoCallConvExtension::Stdcall) ndirectflags |= NDirectMethodDesc::kStdCall; - if (callConv == pmCallConvThiscall) + if (callConv == CorInfoCallConvExtension::Thiscall) ndirectflags |= NDirectMethodDesc::kThisCall; if (pNMD->GetLoaderModule()->IsSystem() && (strcmp(szLibName, "QCall") == 0)) @@ -4481,16 +4439,16 @@ HRESULT FindPredefinedILStubMethod(MethodDesc *pTargetMD, DWORD dwStubFlags, Met #endif // FEATURE_COMINTEROP MethodDesc* CreateInteropILStub( - ILStubState* pss, - StubSigDesc* pSigDesc, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorPinvokeMap unmgdCallConv, - DWORD dwStubFlags, // NDirectStubFlags - int nParamTokens, - mdParamDef* pParamTokenArray, - int iLCIDArg, - bool* pGeneratedNewStub = nullptr + ILStubState* pss, + StubSigDesc* pSigDesc, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags, // NDirectStubFlags + int nParamTokens, + mdParamDef* pParamTokenArray, + int iLCIDArg, + bool* pGeneratedNewStub = nullptr ) { CONTRACT(MethodDesc*) @@ -4779,7 +4737,7 @@ MethodDesc* CreateInteropILStub( { NDirectMethodDesc *pTargetNMD = (NDirectMethodDesc *)pTargetMD; - pTargetNMD->SetStackArgumentSize(cbStackArgSize, (CorPinvokeMap)0); + pTargetNMD->SetStackArgumentSize(cbStackArgSize, MetaSig::GetDefaultUnmanagedCallingConvention()); } #ifdef FEATURE_COMINTEROP else @@ -4802,11 +4760,11 @@ MethodDesc* CreateInteropILStub( } MethodDesc* NDirect::CreateCLRToNativeILStub( - StubSigDesc* pSigDesc, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorPinvokeMap unmgdCallConv, - DWORD dwStubFlags) // NDirectStubFlags + StubSigDesc* pSigDesc, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags) // NDirectStubFlags { CONTRACT(MethodDesc*) { @@ -4935,7 +4893,7 @@ MethodDesc* NDirect::CreateFieldAccessILStub( &sigDesc, (CorNativeLinkType)0, (CorNativeLinkFlags)0, - (CorPinvokeMap)0, + MetaSig::GetDefaultUnmanagedCallingConvention(), dwStubFlags, numParamTokens, pParamTokenArray, @@ -5044,7 +5002,7 @@ MethodDesc* NDirect::CreateStructMarshalILStub(MethodTable* pMT) &sigDesc, (CorNativeLinkType)0, (CorNativeLinkFlags)0, - (CorPinvokeMap)0, + CorInfoCallConvExtension::Managed, dwStubFlags, numParamTokens, pParamTokenArray, @@ -5437,7 +5395,7 @@ void CreateCLRToDispatchCOMStub( mdParamDef* pParamTokenArray = NULL; CreateNDirectStubAccessMetadata(&sigDesc, - (CorPinvokeMap)0, + MetaSig::GetDefaultUnmanagedCallingConvention(), &dwStubFlags, &iLCIDArg, &numArgs); @@ -5452,7 +5410,7 @@ void CreateCLRToDispatchCOMStub( &sigDesc, (CorNativeLinkType)0, (CorNativeLinkFlags)0, - (CorPinvokeMap)0, + MetaSig::GetDefaultUnmanagedCallingConvention(), dwStubFlags | NDIRECTSTUB_FL_COM, pMD, pParamTokenArray, @@ -6694,7 +6652,7 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) GCX_PREEMP(); Signature signature = pVASigCookie->signature; - CorPinvokeMap unmgdCallConv = pmNoMangle; + CorInfoCallConvExtension unmgdCallConv = CorInfoCallConvExtension::Managed; DWORD dwStubFlags = NDIRECTSTUB_FL_BESTFIT; @@ -6708,9 +6666,13 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) BYTE callConv = MetaSig::GetCallingConvention(pVASigCookie->pModule, signature); // Unmanaged calling convention indicates modopt should be read - if (callConv == IMAGE_CEE_CS_CALLCONV_UNMANAGED) + if (callConv != IMAGE_CEE_CS_CALLCONV_UNMANAGED) { - CorUnmanagedCallingConvention callConvMaybe; + unmgdCallConv = (CorInfoCallConvExtension)callConv; + } + else + { + CorInfoCallConvExtension callConvMaybe; UINT errorResID; bool suppressGCTransition = false; HRESULT hr = MetaSig::TryGetUnmanagedCallingConventionFromModOpt(GetScopeHandle(pVASigCookie->pModule), signature.GetRawSig(), signature.GetRawSigLen(), &callConvMaybe, &suppressGCTransition, &errorResID); @@ -6719,11 +6681,11 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) if (hr == S_OK) { - callConv = callConvMaybe; + unmgdCallConv = callConvMaybe; } else { - callConv = MetaSig::GetDefaultUnmanagedCallingConvention(); + unmgdCallConv = MetaSig::GetDefaultUnmanagedCallingConvention(); } if (suppressGCTransition) @@ -6732,9 +6694,6 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) } } - if (!TryConvertCallConvValueToPInvokeCallConv(callConv, &unmgdCallConv)) - COMPlusThrow(kTypeLoadException, IDS_INVALID_PINVOKE_CALLCONV); - LoaderHeap *pHeap = pVASigCookie->pModule->GetLoaderAllocator()->GetHighFrequencyHeap(); PCOR_SIGNATURE new_sig = (PCOR_SIGNATURE)(void *)pHeap->AllocMem(S_SIZE_T(signature.GetRawSigLen())); CopyMemory(new_sig, signature.GetRawSig(), signature.GetRawSigLen()); @@ -6751,7 +6710,7 @@ PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD) dwStubFlags |= NDIRECTSTUB_FL_CONVSIGASVARARG; // vararg P/Invoke must be cdecl - unmgdCallConv = pmCallConvCdecl; + unmgdCallConv = CorInfoCallConvExtension::C; if (((NDirectMethodDesc *)pMD)->IsClassConstructorTriggeredByILStub()) { diff --git a/src/coreclr/vm/dllimport.h b/src/coreclr/vm/dllimport.h index 43a230b5e7fd1..74bd35c1530fa 100644 --- a/src/coreclr/vm/dllimport.h +++ b/src/coreclr/vm/dllimport.h @@ -90,11 +90,11 @@ class NDirect static void PopulateNDirectMethodDesc(NDirectMethodDesc* pNMD, PInvokeStaticSigInfo* pSigInfo); static MethodDesc* CreateCLRToNativeILStub( - StubSigDesc* pSigDesc, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorPinvokeMap unmgdCallConv, - DWORD dwStubFlags); // NDirectStubFlags + StubSigDesc* pSigDesc, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags); // NDirectStubFlags #ifdef FEATURE_COMINTEROP static MethodDesc* CreateFieldAccessILStub( @@ -331,7 +331,7 @@ struct PInvokeStaticSigInfo void ReportErrors(); private: - void InitCallConv(CorPinvokeMap callConv, BOOL bIsVarArg); + void InitCallConv(CorInfoCallConvExtension callConv, BOOL bIsVarArg); void DllImportInit(MethodDesc* pMD, LPCUTF8 *pLibName, LPCUTF8 *pEntryPointName); void PreInit(Module* pModule, MethodTable *pClass); void PreInit(MethodDesc* pMD); @@ -383,13 +383,13 @@ struct PInvokeStaticSigInfo else m_wFlags &= ~PINVOKE_STATIC_SIGINFO_IS_DELEGATE_INTEROP; } - CorPinvokeMap GetCallConv() { LIMITED_METHOD_CONTRACT; return m_callConv; } + CorInfoCallConvExtension GetCallConv() { LIMITED_METHOD_CONTRACT; return m_callConv; } Signature GetSignature() { LIMITED_METHOD_CONTRACT; return m_sig; } private: Module* m_pModule; Signature m_sig; - CorPinvokeMap m_callConv; + CorInfoCallConvExtension m_callConv; WORD m_error; enum @@ -447,7 +447,7 @@ class NDirectStubLinker : public ILStubLinker MethodDesc* pTargetMD, int iLCIDParamIdx); - void SetCallingConvention(CorPinvokeMap unmngCallConv, BOOL fIsVarArg); + void SetCallingConvention(CorInfoCallConvExtension unmngCallConv, BOOL fIsVarArg); void Begin(DWORD dwStubFlags); void End(DWORD dwStubFlags); @@ -569,18 +569,18 @@ class NDirectStubParameters { public: - NDirectStubParameters(Signature sig, - SigTypeContext* pTypeContext, - Module* pModule, - Module* pLoaderModule, - CorNativeLinkType nlType, - CorNativeLinkFlags nlFlags, - CorPinvokeMap unmgdCallConv, - DWORD dwStubFlags, // NDirectStubFlags - int nParamTokens, - mdParamDef* pParamTokenArray, - int iLCIDArg, - MethodTable* pMT + NDirectStubParameters(Signature sig, + SigTypeContext* pTypeContext, + Module* pModule, + Module* pLoaderModule, + CorNativeLinkType nlType, + CorNativeLinkFlags nlFlags, + CorInfoCallConvExtension unmgdCallConv, + DWORD dwStubFlags, // NDirectStubFlags + int nParamTokens, + mdParamDef* pParamTokenArray, + int iLCIDArg, + MethodTable* pMT ) : m_sig(sig), m_pTypeContext(pTypeContext), @@ -598,18 +598,18 @@ class NDirectStubParameters LIMITED_METHOD_CONTRACT; } - Signature m_sig; - SigTypeContext* m_pTypeContext; - Module* m_pModule; - Module* m_pLoaderModule; - mdParamDef* m_pParamTokenArray; - CorPinvokeMap m_unmgdCallConv; - CorNativeLinkType m_nlType; - CorNativeLinkFlags m_nlFlags; - DWORD m_dwStubFlags; - int m_iLCIDArg; - int m_nParamTokens; - MethodTable* m_pMT; + Signature m_sig; + SigTypeContext* m_pTypeContext; + Module* m_pModule; + Module* m_pLoaderModule; + mdParamDef* m_pParamTokenArray; + CorInfoCallConvExtension m_unmgdCallConv; + CorNativeLinkType m_nlType; + CorNativeLinkFlags m_nlFlags; + DWORD m_dwStubFlags; + int m_iLCIDArg; + int m_nParamTokens; + MethodTable* m_pMT; }; PCODE GetILStubForCalli(VASigCookie *pVASigCookie, MethodDesc *pMD); diff --git a/src/coreclr/vm/dllimportcallback.cpp b/src/coreclr/vm/dllimportcallback.cpp index 337876d7b9257..25bb2d4dbb10e 100644 --- a/src/coreclr/vm/dllimportcallback.cpp +++ b/src/coreclr/vm/dllimportcallback.cpp @@ -111,756 +111,6 @@ class UMEntryThunkFreeList static UMEntryThunkFreeList s_thunkFreeList(DEFAULT_THUNK_FREE_LIST_THRESHOLD); -#ifdef TARGET_X86 - -#ifdef FEATURE_STUBS_AS_IL - -EXTERN_C void UMThunkStub(void); - -PCODE UMThunkMarshInfo::GetExecStubEntryPoint() -{ - LIMITED_METHOD_CONTRACT; - - return GetEEFuncEntryPoint(UMThunkStub); -} - -#else // FEATURE_STUBS_AS_IL - -EXTERN_C VOID __cdecl UMThunkStubRareDisable(); -EXTERN_C Thread* __stdcall CreateThreadBlockThrow(); - -// argument stack offsets are multiple of sizeof(SLOT) so we can tag them by OR'ing with 1 -static_assert_no_msg((sizeof(SLOT) & 1) == 0); -#define MAKE_BYVAL_STACK_OFFSET(x) (x) -#define MAKE_BYREF_STACK_OFFSET(x) ((x) | 1) -#define IS_BYREF_STACK_OFFSET(x) ((x) & 1) -#define GET_STACK_OFFSET(x) ((x) & ~1) - -// -1 means not used -#define UNUSED_STACK_OFFSET (UINT)-1 - -// static -VOID UMEntryThunk::CompileUMThunkWorker(UMThunkStubInfo *pInfo, - CPUSTUBLINKER *pcpusl, - UINT *psrcofsregs, // NUM_ARGUMENT_REGISTERS elements - UINT *psrcofs, // pInfo->m_cbDstStack/STACK_ELEM_SIZE elements - UINT retbufofs) // the large structure return buffer ptr arg offset (if any) -{ - STANDARD_VM_CONTRACT; - - CodeLabel* pSetupThreadLabel = pcpusl->NewCodeLabel(); - CodeLabel* pRejoinThreadLabel = pcpusl->NewCodeLabel(); - CodeLabel* pDisableGCLabel = pcpusl->NewCodeLabel(); - CodeLabel* pRejoinGCLabel = pcpusl->NewCodeLabel(); - - // We come into this code with UMEntryThunk in EAX - const X86Reg kEAXentryThunk = kEAX; - - // For ThisCall, we make it look like a normal stdcall so that - // the rest of the code (like repushing the arguments) does not - // have to worry about it. - - if (pInfo->m_wFlags & umtmlThisCall) - { - // pop off the return address into EDX - pcpusl->X86EmitPopReg(kEDX); - - if (pInfo->m_wFlags & umtmlThisCallHiddenArg) - { - // exchange ecx ( "this") with the hidden structure return buffer - // xchg ecx, [esp] - pcpusl->X86EmitOp(0x87, kECX, (X86Reg)kESP_Unsafe); - } - - // jam ecx (the "this" param onto stack. Now it looks like a normal stdcall.) - pcpusl->X86EmitPushReg(kECX); - - // push edx - repush the return address - pcpusl->X86EmitPushReg(kEDX); - } - - // The native signature doesn't have a return buffer - // but the managed signature does. - // Set up the return buffer address here. - if (pInfo->m_wFlags & umtmlBufRetValToEnreg) - { - // Calculate the return buffer address - // Calculate the offset to the return buffer we establish for EAX:EDX below. - // lea edx [esp - offset to EAX:EDX return buffer] - pcpusl->X86EmitEspOffset(0x8d, kEDX, -0xc /* skip return addr, EBP, EBX */ -0x8 /* point to start of EAX:EDX return buffer */ ); - - // exchange edx (which has the return buffer address) - // with the return address - // xchg edx, [esp] - pcpusl->X86EmitOp(0x87, kEDX, (X86Reg)kESP_Unsafe); - - // push edx - pcpusl->X86EmitPushReg(kEDX); - } - - // Setup the EBP frame - pcpusl->X86EmitPushEBPframe(); - - // Save EBX - pcpusl->X86EmitPushReg(kEBX); - - // Make space for return value - instead of repeatedly doing push eax edx pop edx eax - // we will save the return value once and restore it just before returning. - pcpusl->X86EmitSubEsp(sizeof(PCONTEXT(NULL)->Eax) + sizeof(PCONTEXT(NULL)->Edx)); - - // Load thread descriptor into ECX - const X86Reg kECXthread = kECX; - - // save UMEntryThunk - pcpusl->X86EmitPushReg(kEAXentryThunk); - - pcpusl->EmitSetup(pSetupThreadLabel); - - pcpusl->X86EmitMovRegReg(kECX, kEBX); - - pcpusl->EmitLabel(pRejoinThreadLabel); - - // restore UMEntryThunk - pcpusl->X86EmitPopReg(kEAXentryThunk); - -#ifdef _DEBUG - // Save incoming registers - pcpusl->X86EmitPushReg(kEAXentryThunk); // UMEntryThunk - pcpusl->X86EmitPushReg(kECXthread); // thread descriptor - - pcpusl->X86EmitPushReg(kEAXentryThunk); - pcpusl->X86EmitCall(pcpusl->NewExternalCodeLabel((LPVOID) LogUMTransition), 4); - - // Restore registers - pcpusl->X86EmitPopReg(kECXthread); - pcpusl->X86EmitPopReg(kEAXentryThunk); -#endif - -#ifdef PROFILING_SUPPORTED - // Notify profiler of transition into runtime, before we disable preemptive GC - if (CORProfilerTrackTransitions()) - { - // Load the methoddesc into EBX (UMEntryThunk->m_pMD) - pcpusl->X86EmitIndexRegLoad(kEBX, kEAXentryThunk, UMEntryThunk::GetOffsetOfMethodDesc()); - - // Save registers - pcpusl->X86EmitPushReg(kEAXentryThunk); // UMEntryThunk - pcpusl->X86EmitPushReg(kECXthread); // pCurThread - - // Push arguments and notify profiler - pcpusl->X86EmitPushImm32(COR_PRF_TRANSITION_CALL); // Reason - pcpusl->X86EmitPushReg(kEBX); // MethodDesc* - pcpusl->X86EmitCall(pcpusl->NewExternalCodeLabel((LPVOID)ProfilerUnmanagedToManagedTransitionMD), 8); - - // Restore registers - pcpusl->X86EmitPopReg(kECXthread); - pcpusl->X86EmitPopReg(kEAXentryThunk); - - // Push the MethodDesc* (in EBX) for use by the transition on the way out. - pcpusl->X86EmitPushReg(kEBX); - } -#endif // PROFILING_SUPPORTED - - pcpusl->EmitDisable(pDisableGCLabel, TRUE, kECXthread); - - pcpusl->EmitLabel(pRejoinGCLabel); - - // construct a FrameHandlerExRecord - - // push [ECX]Thread.m_pFrame - corresponding to FrameHandlerExRecord::m_pEntryFrame - pcpusl->X86EmitIndexPush(kECXthread, offsetof(Thread, m_pFrame)); - - // push offset FastNExportExceptHandler - pcpusl->X86EmitPushImm32((INT32)(size_t)FastNExportExceptHandler); - - // push fs:[0] - const static BYTE codeSEH1[] = { 0x64, 0xFF, 0x35, 0x0, 0x0, 0x0, 0x0}; - pcpusl->EmitBytes(codeSEH1, sizeof(codeSEH1)); - // EmitBytes doesn't know to increase the stack size - // so we do so manually - pcpusl->SetStackSize(pcpusl->GetStackSize() + 4); - - // link in the exception frame - // mov dword ptr fs:[0], esp - const static BYTE codeSEH2[] = { 0x64, 0x89, 0x25, 0x0, 0x0, 0x0, 0x0}; - pcpusl->EmitBytes(codeSEH2, sizeof(codeSEH2)); - - // EBX will hold address of start of arguments. Calculate here so the AD switch case can access - // the arguments at their original location rather than re-copying them to the inner frame. - // lea ebx, [ebp + 8] - pcpusl->X86EmitIndexLea(kEBX, kEBP, 8); - - // - // ---------------------------------------------------------------------------------------------- - // - // From this point on (until noted) we might be executing as the result of calling into the - // runtime in order to switch AppDomain. In order for the following code to function in both - // scenarios it must be careful when making assumptions about the current stack layout (in the AD - // switch case a new inner frame has been pushed which is not identical to the original outer - // frame). - // - // Our guaranteed state at this point is as follows: - // EAX: Pointer to UMEntryThunk - // EBX: Pointer to start of caller's arguments - // ECX: Pointer to current Thread - // EBP: Equals EBX - 8 (no AD switch) or unspecified (AD switch) - // - // Stack: - // - // +-------------------------+ - // ESP + 0 | | - // - // | Varies | - // - // | | - // +-------------------------+ - // EBX - 20 | Saved Result: EAX/ST(0) | - // +- - - - - - - - - - - - -+ - // EBX - 16 | Saved Result: EDX/ST(0) | - // +-------------------------+ - // EBX - 12 | Caller's EBX | - // +-------------------------+ - // EBX - 8 | Caller's EBP | - // +-------------------------+ - // EBX - 4 | Return address | - // +-------------------------+ - // EBX + 0 | | - // - // | Caller's arguments | - // - // | | - // +-------------------------+ - // - - // save the thread pointer - pcpusl->X86EmitPushReg(kECXthread); - - // reserve the space for call slot - pcpusl->X86EmitSubEsp(4); - - // remember stack size for offset computations - INT iStackSizeAtCallSlot = pcpusl->GetStackSize(); - - if (!(pInfo->m_wFlags & umtmlSkipStub)) - { - // save EDI (it's used by the IL stub invocation code) - pcpusl->X86EmitPushReg(kEDI); - } - - // repush any stack arguments - int arg = pInfo->m_cbDstStack/STACK_ELEM_SIZE; - - while (arg--) - { - if (IS_BYREF_STACK_OFFSET(psrcofs[arg])) - { - // lea ecx, [ebx + ofs] - pcpusl->X86EmitIndexLea(kECX, kEBX, GET_STACK_OFFSET(psrcofs[arg])); - - // push ecx - pcpusl->X86EmitPushReg(kECX); - } - else - { - // push dword ptr [ebx + ofs] - pcpusl->X86EmitIndexPush(kEBX, GET_STACK_OFFSET(psrcofs[arg])); - } - } - - // load register arguments - int regidx = 0; - -#define ARGUMENT_REGISTER(regname) \ - if (psrcofsregs[regidx] != UNUSED_STACK_OFFSET) \ - { \ - if (IS_BYREF_STACK_OFFSET(psrcofsregs[regidx])) \ - { \ - /* lea reg, [ebx + ofs] */ \ - pcpusl->X86EmitIndexLea(k##regname, kEBX, GET_STACK_OFFSET(psrcofsregs[regidx])); \ - } \ - else \ - { \ - /* mov reg, [ebx + ofs] */ \ - pcpusl->X86EmitIndexRegLoad(k##regname, kEBX, GET_STACK_OFFSET(psrcofsregs[regidx])); \ - } \ - } \ - regidx++; - - ENUM_ARGUMENT_REGISTERS_BACKWARD(); - -#undef ARGUMENT_REGISTER - - if (!(pInfo->m_wFlags & umtmlSkipStub)) - { - // - // Call the IL stub which will: - // 1) marshal - // 2) call the managed method - // 3) unmarshal - // - - // the delegate object is extracted by the stub from UMEntryThunk - _ASSERTE(pInfo->m_wFlags & umtmlIsStatic); - - // mov EDI, [EAX + UMEntryThunk.m_pUMThunkMarshInfo] - pcpusl->X86EmitIndexRegLoad(kEDI, kEAXentryThunk, offsetof(UMEntryThunk, m_pUMThunkMarshInfo)); - - // mov EDI, [EDI + UMThunkMarshInfo.m_pILStub] - pcpusl->X86EmitIndexRegLoad(kEDI, kEDI, UMThunkMarshInfo::GetOffsetOfStub()); - - // EAX still contains the UMEntryThunk pointer, so we cannot really use SCRATCHREG - // we can use EDI, though - - INT iCallSlotOffset = pcpusl->GetStackSize() - iStackSizeAtCallSlot; - - // mov [ESP+iCallSlotOffset], EDI - pcpusl->X86EmitIndexRegStore((X86Reg)kESP_Unsafe, iCallSlotOffset, kEDI); - - // call [ESP+iCallSlotOffset] - pcpusl->X86EmitOp(0xff, (X86Reg)2, (X86Reg)kESP_Unsafe, iCallSlotOffset); - - // Emit a NOP so we know that we can call managed code - INDEBUG(pcpusl->Emit8(X86_INSTR_NOP)); - - // restore EDI - pcpusl->X86EmitPopReg(kEDI); - } - else if (!(pInfo->m_wFlags & umtmlIsStatic)) - { - // - // This is call on delegate - // - - // mov THIS, [EAX + UMEntryThunk.m_pObjectHandle] - pcpusl->X86EmitOp(0x8b, THIS_kREG, kEAXentryThunk, offsetof(UMEntryThunk, m_pObjectHandle)); - - // mov THIS, [THIS] - pcpusl->X86EmitOp(0x8b, THIS_kREG, THIS_kREG); - - // - // Inline Delegate.Invoke for perf - // - - // mov SCRATCHREG, [THISREG + Delegate.FP] ; Save target stub in register - pcpusl->X86EmitIndexRegLoad(SCRATCH_REGISTER_X86REG, THIS_kREG, DelegateObject::GetOffsetOfMethodPtr()); - - // mov THISREG, [THISREG + Delegate.OR] ; replace "this" pointer - pcpusl->X86EmitIndexRegLoad(THIS_kREG, THIS_kREG, DelegateObject::GetOffsetOfTarget()); - - INT iCallSlotOffset = pcpusl->GetStackSize() - iStackSizeAtCallSlot; - - // mov [ESP+iCallSlotOffset], SCRATCHREG - pcpusl->X86EmitIndexRegStore((X86Reg)kESP_Unsafe,iCallSlotOffset,SCRATCH_REGISTER_X86REG); - - // call [ESP+iCallSlotOffset] - pcpusl->X86EmitOp(0xff, (X86Reg)2, (X86Reg)kESP_Unsafe, iCallSlotOffset); - - INDEBUG(pcpusl->Emit8(X86_INSTR_NOP)); // Emit a NOP so we know that we can call managed code - } - else - { - // - // Call the managed method - // - - INT iCallSlotOffset = pcpusl->GetStackSize() - iStackSizeAtCallSlot; - - // mov SCRATCH, [SCRATCH + offsetof(UMEntryThunk.m_pManagedTarget)] - pcpusl->X86EmitIndexRegLoad(SCRATCH_REGISTER_X86REG, SCRATCH_REGISTER_X86REG, offsetof(UMEntryThunk, m_pManagedTarget)); - - // mov [ESP+iCallSlotOffset], SCRATCHREG - pcpusl->X86EmitIndexRegStore((X86Reg)kESP_Unsafe, iCallSlotOffset, SCRATCH_REGISTER_X86REG); - - // call [ESP+iCallSlotOffset] - pcpusl->X86EmitOp(0xff, (X86Reg)2, (X86Reg)kESP_Unsafe, iCallSlotOffset); - - INDEBUG(pcpusl->Emit8(X86_INSTR_NOP)); // Emit a NOP so we know that we can call managed code - } - - // skip the call slot - pcpusl->X86EmitAddEsp(4); - - // Save the return value to the outer frame - if (pInfo->m_wFlags & umtmlFpu) - { - // save FP return value - - // fstp qword ptr [ebx - 0x8 - 0xc] - pcpusl->X86EmitOffsetModRM(0xdd, (X86Reg)3, kEBX, -0x8 /* to outer EBP */ -0xc /* skip saved EBP, EBX */); - } - else - { - // save EDX:EAX - if (retbufofs == UNUSED_STACK_OFFSET) - { - pcpusl->X86EmitIndexRegStore(kEBX, -0x8 /* to outer EBP */ -0xc /* skip saved EBP, EBX, EDX */, kEAX); - pcpusl->X86EmitIndexRegStore(kEBX, -0x8 /* to outer EBP */ -0x8 /* skip saved EBP, EBX */, kEDX); - } - // In the umtmlBufRetValToEnreg case, - // we set up the return buffer to output - // into the EDX:EAX buffer we set up for the register return case. - // So we don't need to do more work here. - else if ((pInfo->m_wFlags & umtmlBufRetValToEnreg) == 0) - { - if (pInfo->m_wFlags & umtmlEnregRetValToBuf) - { - pcpusl->X86EmitPushReg(kEDI); // Save EDI register - // Move the return value from the enregistered return from the JIT - // to the return buffer that the native calling convention expects. - // NOTE: Since the managed calling convention does not enregister 8-byte - // struct returns on x86, we only need to handle the single-register 4-byte case. - pcpusl->X86EmitIndexRegLoad(kEDI, kEBX, retbufofs); - pcpusl->X86EmitIndexRegStore(kEDI, 0x0, kEAX); - pcpusl->X86EmitPopReg(kEDI); // Restore EDI register - } - // pretend that the method returned the ret buf hidden argument - // (the structure ptr); C++ compiler seems to rely on this - - // mov dword ptr eax, [ebx + retbufofs] - pcpusl->X86EmitIndexRegLoad(kEAX, kEBX, retbufofs); - - // save it as the return value - pcpusl->X86EmitIndexRegStore(kEBX, -0x8 /* to outer EBP */ -0xc /* skip saved EBP, EBX, EDX */, kEAX); - } - } - - // restore the thread pointer - pcpusl->X86EmitPopReg(kECXthread); - - // - // Once we reach this point in the code we're back to a single scenario: the outer frame of the - // reverse p/invoke. - // - // ---------------------------------------------------------------------------------------------- - // - - // move byte ptr [ecx + Thread.m_fPreemptiveGCDisabled],0 - pcpusl->X86EmitOffsetModRM(0xc6, (X86Reg)0, kECXthread, Thread::GetOffsetOfGCFlag()); - pcpusl->Emit8(0); - - CodeLabel *pRareEnable, *pEnableRejoin; - pRareEnable = pcpusl->NewCodeLabel(); - pEnableRejoin = pcpusl->NewCodeLabel(); - - // test byte ptr [ecx + Thread.m_State], TS_CatchAtSafePoint - pcpusl->X86EmitOffsetModRM(0xf6, (X86Reg)0, kECXthread, Thread::GetOffsetOfState()); - pcpusl->Emit8(Thread::TS_CatchAtSafePoint); - - pcpusl->X86EmitCondJump(pRareEnable,X86CondCode::kJNZ); - - pcpusl->EmitLabel(pEnableRejoin); - - // *** unhook SEH frame - - // mov edx,[esp] ;;pointer to the next exception record - pcpusl->X86EmitEspOffset(0x8B, kEDX, 0); - - // mov dword ptr fs:[0], edx - static const BYTE codeSEH[] = { 0x64, 0x89, 0x15, 0x0, 0x0, 0x0, 0x0 }; - pcpusl->EmitBytes(codeSEH, sizeof(codeSEH)); - - // deallocate SEH frame - pcpusl->X86EmitAddEsp(sizeof(FrameHandlerExRecord)); - -#ifdef PROFILING_SUPPORTED - if (CORProfilerTrackTransitions()) - { - // Load the MethodDesc* we pushed on the entry transition into EBX. - pcpusl->X86EmitPopReg(kEBX); - - // Save registers - pcpusl->X86EmitPushReg(kECX); - - // Push arguments and notify profiler - pcpusl->X86EmitPushImm32(COR_PRF_TRANSITION_RETURN); // Reason - pcpusl->X86EmitPushReg(kEBX); // MethodDesc* - pcpusl->X86EmitCall(pcpusl->NewExternalCodeLabel((LPVOID)ProfilerManagedToUnmanagedTransitionMD), 8); - - // Restore registers - pcpusl->X86EmitPopReg(kECX); - } -#endif // PROFILING_SUPPORTED - - // Load the saved return value - if (pInfo->m_wFlags & umtmlFpu) - { - // fld qword ptr [esp] - pcpusl->Emit8(0xdd); - pcpusl->Emit16(0x2404); - - pcpusl->X86EmitAddEsp(8); - } - else - { - pcpusl->X86EmitPopReg(kEAX); - pcpusl->X86EmitPopReg(kEDX); - } - - // Restore EBX, which was saved in prolog - pcpusl->X86EmitPopReg(kEBX); - - pcpusl->X86EmitPopReg(kEBP); - - //retn n - pcpusl->X86EmitReturn(pInfo->m_cbRetPop); - - //------------------------------------------------------------- - // coming here if the thread is not set up yet - // - - pcpusl->EmitLabel(pSetupThreadLabel); - - // call CreateThreadBlock - pcpusl->X86EmitCall(pcpusl->NewExternalCodeLabel((LPVOID) CreateThreadBlockThrow), 0); - - // mov ecx,eax - pcpusl->Emit16(0xc189); - - // jump back into the main code path - pcpusl->X86EmitNearJump(pRejoinThreadLabel); - - //------------------------------------------------------------- - // coming here if g_TrapReturningThreads was true - // - - pcpusl->EmitLabel(pDisableGCLabel); - - // call UMThunkStubRareDisable. This may throw if we are not allowed - // to enter. Note that we have not set up our SEH yet (deliberately). - // This is important to handle the case where we cannot enter the CLR - // during shutdown and cannot coordinate with the GC because of - // deadlocks. - pcpusl->X86EmitCall(pcpusl->NewExternalCodeLabel((LPVOID) UMThunkStubRareDisable), 0); - - // jump back into the main code path - pcpusl->X86EmitNearJump(pRejoinGCLabel); - - //------------------------------------------------------------- - // Coming here for rare case when enabling GC pre-emptive mode - // - - pcpusl->EmitLabel(pRareEnable); - - // Thread object is expected to be in EBX. So first save caller's EBX - pcpusl->X86EmitPushReg(kEBX); - // mov ebx, ecx - pcpusl->X86EmitMovRegReg(kEBX, kECXthread); - - pcpusl->EmitRareEnable(NULL); - - // restore ebx - pcpusl->X86EmitPopReg(kEBX); - - // return to mainline of function - pcpusl->X86EmitNearJump(pEnableRejoin); -} - -// Compiles an unmanaged to managed thunk for the given signature. -Stub *UMThunkMarshInfo::CompileNExportThunk(LoaderHeap *pLoaderHeap, PInvokeStaticSigInfo* pSigInfo, MetaSig *pMetaSig, BOOL fNoStub) -{ - STANDARD_VM_CONTRACT; - - // stub is always static - BOOL fIsStatic = (fNoStub ? pSigInfo->IsStatic() : TRUE); - - ArgIterator argit(pMetaSig); - - UINT nStackBytes = argit.SizeOfArgStack(); - _ASSERTE((nStackBytes % STACK_ELEM_SIZE) == 0); - - // size of stack passed to us from unmanaged, may be bigger that nStackBytes if there are - // parameters with copy constructors where we perform value-to-reference transformation - UINT nStackBytesIncoming = nStackBytes; - - UINT *psrcofs = (UINT *)_alloca((nStackBytes / STACK_ELEM_SIZE) * sizeof(UINT)); - UINT psrcofsregs[NUM_ARGUMENT_REGISTERS]; - UINT retbufofs = UNUSED_STACK_OFFSET; - - for (int i = 0; i < NUM_ARGUMENT_REGISTERS; i++) - psrcofsregs[i] = UNUSED_STACK_OFFSET; - - UINT nNumArgs = pMetaSig->NumFixedArgs(); - - UINT nOffset = 0; - int numRegistersUsed = 0; - int numStackSlotsIndex = nStackBytes / STACK_ELEM_SIZE; - - // This could have been set in the UnmanagedCallersOnly scenario. - if (m_callConv == UINT16_MAX) - m_callConv = static_cast(pSigInfo->GetCallConv()); - - UMThunkStubInfo stubInfo; - memset(&stubInfo, 0, sizeof(stubInfo)); - - // process this - if (!fIsStatic) - { - // just reserve ECX, instance target is special-cased in the thunk compiler - numRegistersUsed++; - } - - bool hasReturnBuffer = argit.HasRetBuffArg() || (m_callConv == pmCallConvThiscall && argit.HasValueTypeReturn()); - bool hasNativeExchangeTypeReturn = false; - - if (hasReturnBuffer) - { - // If think we have a return buffer, lets make sure that we aren't returning one of the intrinsic native exchange types. - TypeHandle returnType = pMetaSig->GetRetTypeHandleThrowing(); - if (returnType.GetMethodTable()->IsIntrinsicType()) - { - LPCUTF8 pszNamespace; - LPCUTF8 pszTypeName = returnType.GetMethodTable()->GetFullyQualifiedNameInfo(&pszNamespace); - if ((strcmp(pszNamespace, g_InteropServicesNS) == 0) - && (strcmp(pszTypeName, "CLong") == 0 || strcmp(pszTypeName, "CULong") == 0 || strcmp(pszTypeName, "NFloat") == 0)) - { - // We have one of the intrinsic native exchange types. - // As a result, we don't have a return buffer. - hasReturnBuffer = false; - hasNativeExchangeTypeReturn = true; - } - } - } - - // process the return buffer parameter - if (hasReturnBuffer) - { - // Only copy the retbuf arg from the src call when both the managed call and native call - // have a return buffer. - if (argit.HasRetBuffArg()) - { - // managed has a return buffer - if (m_callConv != pmCallConvThiscall && - argit.HasValueTypeReturn() && - pMetaSig->GetReturnTypeSize() == ENREGISTERED_RETURNTYPE_MAXSIZE) - { - // Only managed has a return buffer. - // Native returns in registers. - // We add a flag so the stub correctly sets up the return buffer. - stubInfo.m_wFlags |= umtmlBufRetValToEnreg; - } - numRegistersUsed++; - _ASSERTE(numRegistersUsed - 1 < NUM_ARGUMENT_REGISTERS); - psrcofsregs[NUM_ARGUMENT_REGISTERS - numRegistersUsed] = nOffset; - } - retbufofs = nOffset; - nOffset += StackElemSize(sizeof(LPVOID)); - } - - // process ordinary parameters - for (DWORD i = nNumArgs; i > 0; i--) - { - TypeHandle thValueType; - CorElementType type = pMetaSig->NextArgNormalized(&thValueType); - - UINT cbSize = MetaSig::GetElemSize(type, thValueType); - - BOOL fPassPointer = FALSE; - if (!fNoStub && type == ELEMENT_TYPE_PTR) - { - // this is a copy-constructed argument - get its size - TypeHandle thPtr = pMetaSig->GetLastTypeHandleThrowing(); - - _ASSERTE(thPtr.IsPointer()); - cbSize = thPtr.AsTypeDesc()->GetTypeParam().GetSize(); - - // the incoming stack may be bigger that the outgoing (IL stub) stack - nStackBytesIncoming += (StackElemSize(cbSize) - StackElemSize(sizeof(LPVOID))); - fPassPointer = TRUE; - } - - if (ArgIterator::IsArgumentInRegister(&numRegistersUsed, type, thValueType)) - { - _ASSERTE(numRegistersUsed - 1 < NUM_ARGUMENT_REGISTERS); - psrcofsregs[NUM_ARGUMENT_REGISTERS - numRegistersUsed] = - (fPassPointer ? - MAKE_BYREF_STACK_OFFSET(nOffset) : // the register will get pointer to the incoming stack slot - MAKE_BYVAL_STACK_OFFSET(nOffset)); // the register will get the incoming stack slot - } - else if (fPassPointer) - { - // the stack slot will get pointer to the incoming stack slot - psrcofs[--numStackSlotsIndex] = MAKE_BYREF_STACK_OFFSET(nOffset); - } - else - { - // stack slots will get incoming stack slots (we may need more stack slots for larger parameters) - for (UINT nSlotOfs = StackElemSize(cbSize); nSlotOfs > 0; nSlotOfs -= STACK_ELEM_SIZE) - { - // note the reverse order here which is necessary to maintain - // the original layout of the structure (it'll be reversed once - // more when repushing) - psrcofs[--numStackSlotsIndex] = MAKE_BYVAL_STACK_OFFSET(nOffset + nSlotOfs - STACK_ELEM_SIZE); - } - } - - nOffset += StackElemSize(cbSize); - } - _ASSERTE(numStackSlotsIndex == 0); - - UINT cbActualArgSize = nStackBytesIncoming + (numRegistersUsed * STACK_ELEM_SIZE); - - if (!fIsStatic) - { - // do not count THIS - cbActualArgSize -= StackElemSize(sizeof(LPVOID)); - } - - m_cbActualArgSize = cbActualArgSize; - - if (!FitsInU2(m_cbActualArgSize)) - COMPlusThrow(kMarshalDirectiveException, IDS_EE_SIGTOOCOMPLEX); - - stubInfo.m_cbSrcStack = static_cast(m_cbActualArgSize); - stubInfo.m_cbDstStack = nStackBytes; - - if (m_callConv == pmCallConvCdecl) - { - // caller pop - m_cbRetPop = 0; - } - else - { - // callee pop - m_cbRetPop = static_cast(m_cbActualArgSize); - - if (m_callConv == pmCallConvThiscall) - { - stubInfo.m_wFlags |= umtmlThisCall; - if (argit.HasRetBuffArg()) - { - stubInfo.m_wFlags |= umtmlThisCallHiddenArg; - } - else if (argit.HasValueTypeReturn() && !hasNativeExchangeTypeReturn) - { - stubInfo.m_wFlags |= umtmlThisCallHiddenArg | umtmlEnregRetValToBuf; - // When the native signature has a return buffer but the - // managed one does not, we need to handle popping the - // the return buffer of the stack manually, which we do here. - m_cbRetPop += 4; - } - } - } - - stubInfo.m_cbRetPop = m_cbRetPop; - - if (fIsStatic) stubInfo.m_wFlags |= umtmlIsStatic; - if (fNoStub) stubInfo.m_wFlags |= umtmlSkipStub; - - if (pMetaSig->HasFPReturn()) stubInfo.m_wFlags |= umtmlFpu; - - CPUSTUBLINKER cpusl; - CPUSTUBLINKER *pcpusl = &cpusl; - - // call the worker to emit the actual thunk - UMEntryThunk::CompileUMThunkWorker(&stubInfo, pcpusl, psrcofsregs, psrcofs, retbufofs); - - return pcpusl->Link(pLoaderHeap); -} - -#endif // FEATURE_STUBS_AS_IL - -#else // TARGET_X86 - PCODE UMThunkMarshInfo::GetExecStubEntryPoint() { LIMITED_METHOD_CONTRACT; @@ -868,8 +118,6 @@ PCODE UMThunkMarshInfo::GetExecStubEntryPoint() return m_pILStub; } -#endif // TARGET_X86 - UMEntryThunkCache::UMEntryThunkCache(AppDomain *pDomain) : m_crst(CrstUMEntryThunkCache), m_pDomain(pDomain) @@ -1155,11 +403,6 @@ UMThunkMarshInfo::~UMThunkMarshInfo() } CONTRACTL_END; -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - if (m_pExecStub) - m_pExecStub->DecRef(); -#endif - #ifdef _DEBUG FillMemory(this, sizeof(*this), 0xcc); #endif @@ -1216,11 +459,6 @@ VOID UMThunkMarshInfo::LoadTimeInit(Signature sig, Module * pModule, MethodDesc m_pMD = pMD; m_pModule = pModule; m_sig = sig; - -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - m_callConv = UINT16_MAX; - INDEBUG(m_cbRetPop = 0xcccc;) -#endif } #ifndef CROSSGEN_COMPILE @@ -1244,18 +482,6 @@ VOID UMThunkMarshInfo::RunTimeInit() MethodDesc * pMD = GetMethod(); -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - if (pMD != NULL - && pMD->HasUnmanagedCallersOnlyAttribute()) - { - CorPinvokeMap callConv; - if (TryGetCallingConventionFromUnmanagedCallersOnly(pMD, &callConv)) - { - m_callConv = (UINT16)callConv; - } - } -#endif // TARGET_X86 && !FEATURE_STUBS_AS_IL - // Lookup NGened stub - currently we only support ngening of reverse delegate invoke interop stubs if (pMD != NULL && pMD->IsEEImpl()) { @@ -1273,55 +499,6 @@ VOID UMThunkMarshInfo::RunTimeInit() pFinalILStub = GetStubForInteropMethod(pMD, dwStubFlags, &pStubMD); } -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - PInvokeStaticSigInfo sigInfo; - - if (pMD != NULL) - new (&sigInfo) PInvokeStaticSigInfo(pMD); - else - new (&sigInfo) PInvokeStaticSigInfo(GetSignature(), GetModule()); - - Stub *pFinalExecStub = NULL; - - // we will always emit the argument-shuffling thunk, m_cbActualArgSize is set inside - LoaderHeap *pHeap = (pMD == NULL ? NULL : pMD->GetLoaderAllocator()->GetStubHeap()); - - if (pFinalILStub != NULL || - NDirect::MarshalingRequired(pMD, GetSignature().GetRawSig(), GetModule())) - { - if (pFinalILStub == NULL) - { - DWORD dwStubFlags = 0; - - if (sigInfo.IsDelegateInterop()) - dwStubFlags |= NDIRECTSTUB_FL_DELEGATE; - - pStubMD = GetILStubMethodDesc(pMD, &sigInfo, dwStubFlags); - pFinalILStub = JitILStub(pStubMD); - } - - MetaSig msig(pStubMD); - pFinalExecStub = CompileNExportThunk(pHeap, &sigInfo, &msig, FALSE); - } - else - { - MetaSig msig(GetSignature(), GetModule(), NULL); - pFinalExecStub = CompileNExportThunk(pHeap, &sigInfo, &msig, TRUE); - } - - if (FastInterlockCompareExchangePointer(&m_pExecStub, - pFinalExecStub, - NULL) != NULL) - { - - // Some thread swooped in and set us. Our stub is now a - // duplicate, so throw it away. - if (pFinalExecStub) - pFinalExecStub->DecRef(); - } - -#else // TARGET_X86 && !FEATURE_STUBS_AS_IL - if (pFinalILStub == NULL) { PInvokeStaticSigInfo sigInfo; @@ -1340,148 +517,10 @@ VOID UMThunkMarshInfo::RunTimeInit() pFinalILStub = JitILStub(pStubMD); } -#if defined(TARGET_X86) - MetaSig sig(pMD); - int numRegistersUsed = 0; - UINT16 cbRetPop = 0; - - // - // cbStackArgSize represents the number of arg bytes for the MANAGED signature - // - UINT32 cbStackArgSize = 0; - - int offs = 0; - -#ifdef UNIX_X86_ABI - if (HasRetBuffArgUnmanagedFixup(&sig)) - { - // callee should pop retbuf - numRegistersUsed += 1; - offs += STACK_ELEM_SIZE; - cbRetPop += STACK_ELEM_SIZE; - } -#endif // UNIX_X86_ABI - - for (UINT i = 0 ; i < sig.NumFixedArgs(); i++) - { - TypeHandle thValueType; - CorElementType type = sig.NextArgNormalized(&thValueType); - int cbSize = sig.GetElemSize(type, thValueType); - if (ArgIterator::IsArgumentInRegister(&numRegistersUsed, type, thValueType)) - { - offs += STACK_ELEM_SIZE; - } - else - { - offs += StackElemSize(cbSize); - cbStackArgSize += StackElemSize(cbSize); - } - } - m_cbStackArgSize = cbStackArgSize; - m_cbActualArgSize = (pStubMD != NULL) ? pStubMD->AsDynamicMethodDesc()->GetNativeStackArgSize() : offs; - - PInvokeStaticSigInfo sigInfo; - if (pMD != NULL) - new (&sigInfo) PInvokeStaticSigInfo(pMD); - else - new (&sigInfo) PInvokeStaticSigInfo(GetSignature(), GetModule()); - if (sigInfo.GetCallConv() == pmCallConvCdecl) - { - m_cbRetPop = cbRetPop; - } - else - { - // For all the other calling convention except cdecl, callee pops the stack arguments - m_cbRetPop = cbRetPop + static_cast(m_cbActualArgSize); - } -#endif // TARGET_X86 - -#endif // TARGET_X86 && !FEATURE_STUBS_AS_IL - // Must be the last thing we set! InterlockedCompareExchangeT(&m_pILStub, pFinalILStub, (PCODE)1); } -#if defined(TARGET_X86) && defined(FEATURE_STUBS_AS_IL) -VOID UMThunkMarshInfo::SetupArguments(char *pSrc, ArgumentRegisters *pArgRegs, char *pDst) -{ - MethodDesc *pMD = GetMethod(); - - _ASSERTE(pMD); - - // - // x86 native uses the following stack layout: - // | saved eip | - // | --------- | <- CFA - // | stkarg 0 | - // | stkarg 1 | - // | ... | - // | stkarg N | - // - // x86 managed, however, uses a bit different stack layout: - // | saved eip | - // | --------- | <- CFA - // | stkarg M | (NATIVE/MANAGE may have different number of stack arguments) - // | ... | - // | stkarg 1 | - // | stkarg 0 | - // - // This stub bridges the gap between them. - // - char *pCurSrc = pSrc; - char *pCurDst = pDst + m_cbStackArgSize; - - MetaSig sig(pMD); - - int numRegistersUsed = 0; - -#ifdef UNIX_X86_ABI - if (HasRetBuffArgUnmanagedFixup(&sig)) - { - // Pass retbuf via Ecx - numRegistersUsed += 1; - pArgRegs->Ecx = *((UINT32 *)pCurSrc); - pCurSrc += STACK_ELEM_SIZE; - } -#endif // UNIX_X86_ABI - - for (UINT i = 0 ; i < sig.NumFixedArgs(); i++) - { - TypeHandle thValueType; - CorElementType type = sig.NextArgNormalized(&thValueType); - int cbSize = sig.GetElemSize(type, thValueType); - int elemSize = StackElemSize(cbSize); - - if (ArgIterator::IsArgumentInRegister(&numRegistersUsed, type, thValueType)) - { - _ASSERTE(elemSize == STACK_ELEM_SIZE); - - if (numRegistersUsed == 1) - pArgRegs->Ecx = *((UINT32 *)pCurSrc); - else if (numRegistersUsed == 2) - pArgRegs->Edx = *((UINT32 *)pCurSrc); - } - else - { - pCurDst -= elemSize; - memcpy(pCurDst, pCurSrc, elemSize); - } - - pCurSrc += elemSize; - } - - _ASSERTE(pDst == pCurDst); -} - -EXTERN_C VOID STDCALL UMThunkStubSetupArgumentsWorker(UMThunkMarshInfo *pMarshInfo, - char *pSrc, - UMThunkMarshInfo::ArgumentRegisters *pArgRegs, - char *pDst) -{ - pMarshInfo->SetupArguments(pSrc, pArgRegs, pDst); -} -#endif // TARGET_X86 && FEATURE_STUBS_AS_IL - #ifdef _DEBUG void STDCALL LogUMTransition(UMEntryThunk* thunk) { @@ -1532,7 +571,7 @@ namespace } } -bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorPinvokeMap* pCallConv) +bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorInfoCallConvExtension* pCallConv) { STANDARD_VM_CONTRACT; _ASSERTE(pMD != NULL && pMD->HasUnmanagedCallersOnlyAttribute()); @@ -1568,7 +607,6 @@ bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorPinvoke if (nativeCallableInternalData) { namedArgs[0].InitI4FieldEnum("CallingConvention", "System.Runtime.InteropServices.CallingConvention", (ULONG)(CorPinvokeMap)0); - namedArgs[0].InitI4FieldEnum("CallingConvention", "System.Runtime.InteropServices.CallingConvention", (ULONG)(CorPinvokeMap)0); } else { @@ -1596,15 +634,15 @@ bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorPinvoke if (namedArgs[0].val.type.tag == SERIALIZATION_TYPE_UNDEFINED) return false; - CorPinvokeMap callConvLocal = (CorPinvokeMap)0; + CorInfoCallConvExtension callConvLocal; if (nativeCallableInternalData) { - callConvLocal = (CorPinvokeMap)(namedArgs[0].val.u4 << 8); + callConvLocal = (CorInfoCallConvExtension)(namedArgs[0].val.u4 << 8); } else { // Set WinAPI as the default - callConvLocal = CorPinvokeMap::pmCallConvWinapi; + callConvLocal = MetaSig::GetDefaultUnmanagedCallingConvention(); CaValue* arrayOfTypes = &namedArgs[0].val; for (ULONG i = 0; i < arrayOfTypes->arr.length; i++) @@ -1617,19 +655,19 @@ bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorPinvoke // in Fully Qualified form, so we include the ',' delimiter. if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvCdecl,")) { - callConvLocal = CorPinvokeMap::pmCallConvCdecl; + callConvLocal = CorInfoCallConvExtension::C; } else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvStdcall,")) { - callConvLocal = CorPinvokeMap::pmCallConvStdcall; + callConvLocal = CorInfoCallConvExtension::Stdcall; } else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvFastcall,")) { - callConvLocal = CorPinvokeMap::pmCallConvFastcall; + callConvLocal = CorInfoCallConvExtension::Fastcall; } else if (BeginsWith(typeNameValue.str.cbStr, typeNameValue.str.pStr, "System.Runtime.CompilerServices.CallConvThiscall,")) { - callConvLocal = CorPinvokeMap::pmCallConvThiscall; + callConvLocal = CorInfoCallConvExtension::Thiscall; } } } diff --git a/src/coreclr/vm/dllimportcallback.h b/src/coreclr/vm/dllimportcallback.h index f0628da5b4fd0..eb884db91b818 100644 --- a/src/coreclr/vm/dllimportcallback.h +++ b/src/coreclr/vm/dllimportcallback.h @@ -16,33 +16,6 @@ #include "class.h" #include "dllimport.h" -enum UMThunkStubFlags -{ - umtmlIsStatic = 0x0001, - umtmlThisCall = 0x0002, - umtmlThisCallHiddenArg = 0x0004, - umtmlFpu = 0x0008, - umtmlEnregRetValToBuf = 0x0010, - umtmlBufRetValToEnreg = 0x0020, -#ifdef TARGET_X86 - // the signature is trivial so stub need not be generated and the target can be called directly - umtmlSkipStub = 0x0080, -#endif // TARGET_X86 -}; - -#include -//-------------------------------------------------------------------------- -// This structure captures basic info needed to build an UMThunk. -//-------------------------------------------------------------------------- -struct UMThunkStubInfo -{ - UINT32 m_cbDstStack; //# of bytes of stack portion of managed args - UINT16 m_cbSrcStack; //# of bytes of stack portion of unmanaged args - UINT16 m_cbRetPop; //# of bytes to pop on return to unmanaged - UINT16 m_wFlags; // UMThunkStubFlags enum -}; -#include - //---------------------------------------------------------------------- // This structure collects all information needed to marshal an // unmanaged->managed thunk. The only information missing is the @@ -110,46 +83,7 @@ class UMThunkMarshInfo return m_pMD; } -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - PCODE GetExecStubEntryPoint() - { - WRAPPER_NO_CONTRACT; - return GetExecStub()->GetEntryPoint(); - } - - Stub* GetExecStub() - { - CONTRACT (Stub*) - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - PRECONDITION(IsCompletelyInited()); - POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); - } - CONTRACT_END; - - RETURN m_pExecStub; - } - - UINT16 GetCbRetPop() - { - CONTRACTL - { - NOTHROW; - GC_NOTRIGGER; - MODE_ANY; - SUPPORTS_DAC; - PRECONDITION(IsCompletelyInited()); - } - CONTRACTL_END; - - return m_cbRetPop; - } - -#else PCODE GetExecStubEntryPoint(); -#endif BOOL IsCompletelyInited() { @@ -165,41 +99,9 @@ class UMThunkMarshInfo return (UINT32)offsetof(UMThunkMarshInfo, m_pILStub); } -#ifdef TARGET_X86 - -#ifdef FEATURE_STUBS_AS_IL - struct ArgumentRegisters - { - UINT32 Ecx; - UINT32 Edx; - }; - - VOID SetupArguments(char *pSrc, ArgumentRegisters *pArgRegs, char *pDst); -#else -private: - // Compiles an unmanaged to managed thunk for the given signature. The thunk - // will call the stub or, if fNoStub == TRUE, directly the managed target. - Stub *CompileNExportThunk(LoaderHeap *pLoaderHeap, PInvokeStaticSigInfo* pSigInfo, MetaSig *pMetaSig, BOOL fNoStub); -#endif // FEATURE_STUBS_AS_IL - -#endif // TARGET_X86 - private: PCODE m_pILStub; // IL stub for marshaling - // On x86, NULL for no-marshal signatures // On non-x86, the managed entrypoint for no-delegate no-marshal signatures -#ifdef TARGET_X86 - UINT32 m_cbActualArgSize; // caches m_pSig.SizeOfFrameArgumentArray() - // On x86/Linux we have to augment with numRegistersUsed * STACK_ELEM_SIZE - UINT16 m_cbRetPop; // stack bytes popped by callee (for UpdateRegDisplay) -#ifdef FEATURE_STUBS_AS_IL - UINT32 m_cbStackArgSize; // stack bytes pushed for managed code -#else - Stub* m_pExecStub; // UMEntryThunk jumps directly here - UINT16 m_callConv; // unmanaged calling convention and flags (CorPinvokeMap) -#endif // FEATURE_STUBS_AS_IL -#endif // TARGET_X86 - MethodDesc * m_pMD; // maybe null Module * m_pModule; Signature m_sig; @@ -234,23 +136,6 @@ class UMEntryThunk static UMEntryThunk* CreateUMEntryThunk(); static VOID FreeUMEntryThunk(UMEntryThunk* p); -#if defined(TARGET_X86) && !defined(FEATURE_STUBS_AS_IL) - // Compiles an unmanaged to managed thunk with the given calling convention adaptation. - // - psrcofsregs are stack offsets that should be loaded to argument registers (ECX, EDX) - // - psrcofs are stack offsets that should be repushed for the managed target - // - retbufofs is the offset of the hidden byref structure argument when returning large - // structures; -1 means there is none - // Special values recognized by psrcofsregs and psrcofs are -1 which means not present - // and 1 which means that this register/stack slot should get the UMEntryThunk pointer. - // This method is used for all reverse P/Invoke calls on x86 (the umtmlSkipStub - // flag determines whether the managed target is stub or the actual target method). - static VOID CompileUMThunkWorker(UMThunkStubInfo *pInfo, - CPUSTUBLINKER *pcpusl, - UINT *psrcofsregs, - UINT *psrcofs, - UINT retbufofs); -#endif // TARGET_X86 && !FEATURE_STUBS_AS_IL - #ifndef DACCESS_COMPILE VOID LoadTimeInit(PCODE pManagedTarget, OBJECTHANDLE pObjectHandle, @@ -527,7 +412,7 @@ EXCEPTION_HANDLER_DECL(UMThunkPrestubHandler); #endif // TARGET_X86 && !FEATURE_STUBS_AS_IL -bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorPinvokeMap* pCallConv); +bool TryGetCallingConventionFromUnmanagedCallersOnly(MethodDesc* pMD, CorInfoCallConvExtension* pCallConv); extern "C" void TheUMEntryPrestub(void); extern "C" PCODE TheUMEntryPrestubWorker(UMEntryThunk * pUMEntryThunk); diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 0823c4f7ee803..378f05b5dee1d 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -813,13 +813,11 @@ FCFuncStart(gInterlockedFuncs) FCFuncElementSig("Exchange", &gsig_SM_RefDbl_Dbl_RetDbl, COMInterlocked::ExchangeDouble) FCFuncElementSig("Exchange", &gsig_SM_RefFlt_Flt_RetFlt, COMInterlocked::ExchangeFloat) FCFuncElementSig("Exchange", &gsig_SM_RefObj_Obj_RetObj, COMInterlocked::ExchangeObject) - FCFuncElementSig("Exchange", &gsig_SM_RefIntPtr_IntPtr_RetIntPtr, COMInterlocked::ExchangePointer) FCIntrinsicSig("CompareExchange", &gsig_SM_RefInt_Int_Int_RetInt, COMInterlocked::CompareExchange, CORINFO_INTRINSIC_InterlockedCmpXchg32) FCIntrinsicSig("CompareExchange", &gsig_SM_RefLong_Long_Long_RetLong, COMInterlocked::CompareExchange64, CORINFO_INTRINSIC_InterlockedCmpXchg64) FCFuncElementSig("CompareExchange", &gsig_SM_RefDbl_Dbl_Dbl_RetDbl, COMInterlocked::CompareExchangeDouble) FCFuncElementSig("CompareExchange", &gsig_SM_RefFlt_Flt_Flt_RetFlt, COMInterlocked::CompareExchangeFloat) FCFuncElementSig("CompareExchange", &gsig_SM_RefObj_Obj_Obj_RetObj, COMInterlocked::CompareExchangeObject) - FCFuncElementSig("CompareExchange", &gsig_SM_RefIntPtr_IntPtr_IntPtr_RetIntPtr, COMInterlocked::CompareExchangePointer) FCIntrinsicSig("ExchangeAdd", &gsig_SM_RefInt_Int_RetInt, COMInterlocked::ExchangeAdd32, CORINFO_INTRINSIC_InterlockedXAdd32) FCIntrinsicSig("ExchangeAdd", &gsig_SM_RefLong_Long_RetLong, COMInterlocked::ExchangeAdd64, CORINFO_INTRINSIC_InterlockedXAdd64) diff --git a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h index 638f574ece8ad..3fa688b60e648 100644 --- a/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h +++ b/src/coreclr/vm/eventing/eventpipe/ep-rt-coreclr.h @@ -1891,7 +1891,7 @@ ep_rt_execute_rundown (void) // STATIC_CONTRACT_NOTHROW #undef ep_rt_object_array_alloc -#define ep_rt_object_array_alloc(obj_type,size) (new (nothrow) obj_type [size]) +#define ep_rt_object_array_alloc(obj_type,size) (new (nothrow) obj_type [size]()) // STATIC_CONTRACT_NOTHROW #undef ep_rt_object_array_free diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index d280e724be50b..1d5180349be8a 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2787,6 +2787,10 @@ class UMThkCallFrame : public UnmanagedToManagedFrame struct ReversePInvokeFrame { Thread* currentThread; + MethodDesc* pMD; +#ifndef FEATURE_EH_FUNCLETS + FrameHandlerExRecord record; +#endif }; #if defined(TARGET_X86) && defined(FEATURE_COMINTEROP) diff --git a/src/coreclr/vm/i386/asmconstants.h b/src/coreclr/vm/i386/asmconstants.h index 53813933150ea..9c2397458d04d 100644 --- a/src/coreclr/vm/i386/asmconstants.h +++ b/src/coreclr/vm/i386/asmconstants.h @@ -293,14 +293,6 @@ ASMCONSTANTS_C_ASSERT(UMEntryThunk__m_pUMThunkMarshInfo == offsetof(UMEntryThunk #define UMThunkMarshInfo__m_pILStub 0x00 ASMCONSTANTS_C_ASSERT(UMThunkMarshInfo__m_pILStub == offsetof(UMThunkMarshInfo, m_pILStub)) -#define UMThunkMarshInfo__m_cbActualArgSize 0x04 -ASMCONSTANTS_C_ASSERT(UMThunkMarshInfo__m_cbActualArgSize == offsetof(UMThunkMarshInfo, m_cbActualArgSize)) - -#ifdef FEATURE_STUBS_AS_IL -#define UMThunkMarshInfo__m_cbRetPop 0x08 -ASMCONSTANTS_C_ASSERT(UMThunkMarshInfo__m_cbRetPop == offsetof(UMThunkMarshInfo, m_cbRetPop)) -#endif //FEATURE_STUBS_AS_IL - // For JIT_PInvokeBegin and JIT_PInvokeEnd helpers #define Frame__m_Next 0x04 ASMCONSTANTS_C_ASSERT(Frame__m_Next == offsetof(Frame, m_Next)); diff --git a/src/coreclr/vm/i386/cgencpu.h b/src/coreclr/vm/i386/cgencpu.h index 7a0ef55aec80a..04de3f584ae9e 100644 --- a/src/coreclr/vm/i386/cgencpu.h +++ b/src/coreclr/vm/i386/cgencpu.h @@ -102,19 +102,14 @@ EXTERN_C void SinglecastDelegateInvokeStub(); // Parameter size //********************************************************************** -typedef INT32 StackElemType; -#define STACK_ELEM_SIZE sizeof(StackElemType) - - +inline unsigned StackElemSize(unsigned parmSize, bool isValueType = false /* unused */, bool isFloatHfa = false /* unused */) +{ + const unsigned stackSlotSize = 4; + return ALIGN_UP(parmSize, stackSlotSize); +} #include "stublinkerx86.h" - - -// !! This expression assumes STACK_ELEM_SIZE is a power of 2. -#define StackElemSize(parmSize) (((parmSize) + STACK_ELEM_SIZE - 1) & ~((ULONG)(STACK_ELEM_SIZE - 1))) - - //********************************************************************** // Frames //********************************************************************** diff --git a/src/coreclr/vm/i386/excepx86.cpp b/src/coreclr/vm/i386/excepx86.cpp index c61337426b5f7..75e42c2b34e86 100644 --- a/src/coreclr/vm/i386/excepx86.cpp +++ b/src/coreclr/vm/i386/excepx86.cpp @@ -171,7 +171,8 @@ Frame *GetCurrFrame(EXCEPTION_REGISTRATION_RECORD *pEstablisherFrame) else pFrame = ((FrameHandlerExRecord *)pEstablisherFrame)->GetCurrFrame(); - _ASSERTE(GetThread() == NULL || GetThread()->GetFrame() <= pFrame); + // Assert that the exception frame is on the thread or that the exception frame is the top frame. + _ASSERTE(GetThread() == NULL || GetThread()->GetFrame() == (Frame*)-1 || GetThread()->GetFrame() <= pFrame); return pFrame; } @@ -2012,8 +2013,8 @@ BOOL PopNestedExceptionRecords(LPVOID pTargetSP, BOOL bCheckForUnknownHandlers) while ((LPVOID)pEHR < pTargetSP) { // - // The only handler type we're allowed to have below the limit on the FS:0 chain in these cases is a nested - // exception record, so we verify that here. + // The only handler types we're allowed to have below the limit on the FS:0 chain in these cases are a + // nested exception record or a fast NExport record, so we verify that here. // // There is a special case, of course: for an unhandled exception, when the default handler does the exit // unwind, we may have an exception that escapes a finally clause, thus replacing the original unhandled @@ -2025,6 +2026,7 @@ BOOL PopNestedExceptionRecords(LPVOID pTargetSP, BOOL bCheckForUnknownHandlers) // handler that we're removing, and that's the important point. The handler that ExecuteHandler2 pushes // isn't a public export from ntdll, but its named "UnwindHandler" and is physically shortly after // ExecuteHandler2 in ntdll. + // In this case, we don't want to pop off the NExportSEH handler since it's our outermost handler. // static HINSTANCE ExecuteHandler2Module = 0; static BOOL ExecuteHandler2ModuleInited = FALSE; @@ -2048,8 +2050,8 @@ BOOL PopNestedExceptionRecords(LPVOID pTargetSP, BOOL bCheckForUnknownHandlers) else { // Note: if we can't find the module containing ExecuteHandler2, we'll just be really strict and require - // that we're only popping nested handlers. - _ASSERTE(IsComPlusNestedExceptionRecord(pEHR) || + // that we're only popping nested handlers or the FastNExportSEH handler. + _ASSERTE(FastNExportSEH(pEHR) || IsComPlusNestedExceptionRecord(pEHR) || ((ExecuteHandler2Module != NULL) && IsIPInModule(ExecuteHandler2Module, (PCODE)pEHR->Handler))); } #endif // _DEBUG @@ -2248,7 +2250,11 @@ StackWalkAction COMPlusThrowCallback( // SWA value if (!pExInfo->m_pPrevNestedInfo) { if (pData->pCurrentExceptionRecord) { if (pFrame) _ASSERTE(pData->pCurrentExceptionRecord > pFrame); - if (pCf->IsFrameless()) _ASSERTE((ULONG_PTR)pData->pCurrentExceptionRecord >= GetRegdisplaySP(pCf->GetRegisterSet())); + // The FastNExport SEH handler can be in the frame we just unwound and as a result just out of range. + if (pCf->IsFrameless() && !FastNExportSEH((PEXCEPTION_REGISTRATION_RECORD)pData->pCurrentExceptionRecord)) + { + _ASSERTE((ULONG_PTR)pData->pCurrentExceptionRecord >= GetRegdisplaySP(pCf->GetRegisterSet())); + } } if (pData->pPrevExceptionRecord) { // FCALLS have an extra SEH record in debug because of the desctructor diff --git a/src/coreclr/vm/i386/umthunkstub.S b/src/coreclr/vm/i386/umthunkstub.S index d24493ea0de38..24392b3fd8268 100644 --- a/src/coreclr/vm/i386/umthunkstub.S +++ b/src/coreclr/vm/i386/umthunkstub.S @@ -20,149 +20,3 @@ NESTED_ENTRY TheUMEntryPrestub, _TEXT, UnhandledExceptionHandlerUnix jmp eax // Tail Jmp #undef STK_ALIGN_PADDING NESTED_END TheUMEntryPrestub, _TEXT - -// -// eax: UMEntryThunk* -// -NESTED_ENTRY UMThunkStub, _TEXT, UnhandledExceptionHandlerUnix - -#define UMThunkStub_SAVEDREG (3*4) // ebx, esi, edi -#define UMThunkStub_LOCALVARS (2*4) // UMEntryThunk*, Thread* -#define UMThunkStub_UMENTRYTHUNK_OFFSET (UMThunkStub_SAVEDREG+4) -#define UMThunkStub_THREAD_OFFSET (UMThunkStub_UMENTRYTHUNK_OFFSET+4) -#define UMThunkStub_INT_ARG_OFFSET (UMThunkStub_THREAD_OFFSET+4) -#define UMThunkStub_FIXEDALLOCSIZE (UMThunkStub_LOCALVARS+4) // extra 4 is for stack alignment - -// return address <-- entry ESP -// saved ebp <-- EBP -// saved ebx -// saved esi -// saved edi -// UMEntryThunk* -// Thread* -// dummy 4 byte for 16 byte stack alignment -// {optional stack args passed to callee} <-- new esp - - PROLOG_BEG - PROLOG_PUSH ebx - PROLOG_PUSH esi - PROLOG_PUSH edi - PROLOG_END - sub esp, UMThunkStub_FIXEDALLOCSIZE - - mov dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET], eax - - call C_FUNC(GetThread) - test eax, eax - jz LOCAL_LABEL(DoThreadSetup) - -LOCAL_LABEL(HaveThread): - - mov dword ptr [ebp - UMThunkStub_THREAD_OFFSET], eax - - // FailFast if a method marked UnmanagedCallersOnlyAttribute is invoked via ldftn and calli. - cmp dword ptr [eax + Thread_m_fPreemptiveGCDisabled], 1 - jz LOCAL_LABEL(InvalidTransition) - - // disable preemptive GC - mov dword ptr [eax + Thread_m_fPreemptiveGCDisabled], 1 - - // catch returning thread here if a GC is in progress - PREPARE_EXTERNAL_VAR g_TrapReturningThreads, eax - cmp eax, 0 - jnz LOCAL_LABEL(DoTrapReturningThreadsTHROW) - -LOCAL_LABEL(InCooperativeMode): - - mov eax, dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET] - mov ebx, dword ptr [eax + UMEntryThunk__m_pUMThunkMarshInfo] - mov eax, dword ptr [ebx + UMThunkMarshInfo__m_cbActualArgSize] - test eax, eax - jnz LOCAL_LABEL(UMThunkStub_CopyStackArgs) - -LOCAL_LABEL(UMThunkStub_ArgumentsSetup): - - mov eax, dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET] - mov ebx, dword ptr [eax + UMEntryThunk__m_pUMThunkMarshInfo] - mov ebx, dword ptr [ebx + UMThunkMarshInfo__m_pILStub] - - call ebx - -LOCAL_LABEL(PostCall): - - mov ebx, dword ptr [ebp - UMThunkStub_THREAD_OFFSET] - mov dword ptr [ebx + Thread_m_fPreemptiveGCDisabled], 0 - - lea esp, [ebp - UMThunkStub_SAVEDREG] // deallocate arguments - - mov ecx, dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET] - mov edx, dword ptr [ecx + UMEntryThunk__m_pUMThunkMarshInfo] - mov edx, dword ptr [edx + UMThunkMarshInfo__m_cbRetPop] - - EPILOG_BEG - EPILOG_POP edi - EPILOG_POP esi - EPILOG_POP ebx - EPILOG_END - - pop ecx // pop return address - add esp, edx // adjust ESP - jmp ecx // return to caller - -LOCAL_LABEL(DoThreadSetup): - - call C_FUNC(CreateThreadBlockThrow) - jmp LOCAL_LABEL(HaveThread) - -LOCAL_LABEL(InvalidTransition): - - //No arguments to setup , ReversePInvokeBadTransition will failfast - call C_FUNC(ReversePInvokeBadTransition) - -LOCAL_LABEL(DoTrapReturningThreadsTHROW): - - // extern "C" VOID STDCALL UMThunkStubRareDisableWorker(Thread *pThread, UMEntryThunk *pUMEntryThunk) - sub esp, (2*4) // add padding to ensure 16 byte stack alignment - mov eax, dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET] - push eax - mov eax, dword ptr [ebp - UMThunkStub_THREAD_OFFSET] - push eax - call C_FUNC(UMThunkStubRareDisableWorker) - add esp, (2*4) // restore to before stack alignment - - jmp LOCAL_LABEL(InCooperativeMode) - -LOCAL_LABEL(UMThunkStub_CopyStackArgs): - - // eax = m_cbActualArgSize (in bytes) - - sub esp, eax - and esp, -16 // align with 16 byte - lea edi, [esp] // edi = dest - - lea esi, [ebp + 0x8] // esi = src - - // - // EXTERN_C VOID STDCALL UMThunkStubSetupArgumentsWorker(UMThunkMarshInfo *pMarshInfo, - // char *pSrc, - // UMThunkMarshInfo::ArgumentRegisters *pArgRegs, - // char *pDst) - push edx - push ecx - lea ecx, [esp] - - sub esp, 8 // Pad - push edi // pSrc - push ecx // pArgRegs - push esi // pSrc - mov ecx, dword ptr [ebp - UMThunkStub_UMENTRYTHUNK_OFFSET] - mov ecx, dword ptr [ecx + UMEntryThunk__m_pUMThunkMarshInfo] - push ecx // pMarshInfo - CHECK_STACK_ALIGNMENT - call C_FUNC(UMThunkStubSetupArgumentsWorker) - add esp, 8 - pop ecx - pop edx - jmp LOCAL_LABEL(UMThunkStub_ArgumentsSetup) - -NESTED_END UMThunkStub, _TEXT diff --git a/src/coreclr/vm/ilmarshalers.cpp b/src/coreclr/vm/ilmarshalers.cpp index 33271612b3c67..564cc8f10a95e 100644 --- a/src/coreclr/vm/ilmarshalers.cpp +++ b/src/coreclr/vm/ilmarshalers.cpp @@ -3350,13 +3350,24 @@ MarshalerOverrideStatus ILBlittableValueClassWithCopyCtorMarshaler::ArgumentOver else { // nothing to do but pass the value along - // note that on x86 the argument comes by-value but is converted to pointer by the UM thunk - // so that we don't make copies that would not be accounted for by copy ctors + // note that on x86 the argument comes by-value + // but on other platforms it comes by-reference +#ifdef TARGET_X86 + LocalDesc locDesc(pargs->mm.m_pMT); + pslIL->SetStubTargetArgType(&locDesc); + + DWORD dwNewValueTypeLocal; + dwNewValueTypeLocal = pslIL->NewLocal(locDesc); + pslILDispatch->EmitLDARG(argidx); + pslILDispatch->EmitSTLOC(dwNewValueTypeLocal); + pslILDispatch->EmitLDLOCA(dwNewValueTypeLocal); +#else LocalDesc locDesc(pargs->mm.m_pMT); locDesc.MakeCopyConstructedPointer(); - pslIL->SetStubTargetArgType(&locDesc); // native type is a pointer + pslIL->SetStubTargetArgType(&locDesc); pslILDispatch->EmitLDARG(argidx); +#endif return OVERRIDDEN; } diff --git a/src/coreclr/vm/ilstubcache.cpp b/src/coreclr/vm/ilstubcache.cpp index 64bd551b2c12a..ffeca0ae8de21 100644 --- a/src/coreclr/vm/ilstubcache.cpp +++ b/src/coreclr/vm/ilstubcache.cpp @@ -285,10 +285,7 @@ MethodDesc* ILStubCache::CreateNewMethodDesc(LoaderHeap* pCreationHeap, MethodTa // mark certain types of stub MDs with random flags so ILStubManager recognizes them if (SF_IsReverseStub(dwStubFlags)) { - pMD->m_dwExtendedFlags |= DynamicMethodDesc::nomdReverseStub; -#if !defined(TARGET_X86) - pMD->m_dwExtendedFlags |= DynamicMethodDesc::nomdUnmanagedCallersOnlyStub; -#endif + pMD->m_dwExtendedFlags |= DynamicMethodDesc::nomdReverseStub | DynamicMethodDesc::nomdUnmanagedCallersOnlyStub; pMD->GetILStubResolver()->SetStubType(ILStubResolver::NativeToCLRInteropStub); } else diff --git a/src/coreclr/vm/interoplibinterface.cpp b/src/coreclr/vm/interoplibinterface.cpp index ce181ce9ccd10..81b114f02ad38 100644 --- a/src/coreclr/vm/interoplibinterface.cpp +++ b/src/coreclr/vm/interoplibinterface.cpp @@ -716,10 +716,10 @@ namespace extObjCxt = cache->Find(cacheKey); // If is no object found in the cache, check if the object COM instance is actually the CCW - // representing a managed object. For the scenario of marshalling through a global instance, - // COM instances that are actually CCWs should be unwrapped to the original managed object - // to allow for round-tripping object -> COM instance -> object. - if (extObjCxt == NULL && scenario == ComWrappersScenario::MarshallingGlobalInstance) + // representing a managed object. If the user passed the Unwrap flag, COM instances that are + // actually CCWs should be unwrapped to the original managed object to allow for round + // tripping object -> COM instance -> object. + if (extObjCxt == NULL && (flags & CreateObjectFlags::CreateObjectFlags_Unwrap)) { // If the COM instance is a CCW that is not COM-activated, use the object of that wrapper object. InteropLib::OBJECTHANDLE handleLocal; @@ -1308,6 +1308,7 @@ BOOL QCALLTYPE ComWrappersNative::TryGetOrCreateObjectForComInstance( _In_ QCall::ObjectHandleOnStack comWrappersImpl, _In_ INT64 wrapperId, _In_ void* ext, + _In_opt_ void* innerMaybe, _In_ INT32 flags, _In_ QCall::ObjectHandleOnStack wrapperMaybe, _Inout_ QCall::ObjectHandleOnStack retValue) @@ -1322,25 +1323,17 @@ BOOL QCALLTYPE ComWrappersNative::TryGetOrCreateObjectForComInstance( HRESULT hr; IUnknown* externalComObject = reinterpret_cast(ext); + IUnknown* inner = reinterpret_cast(innerMaybe); - // Determine the true identity of the object + // Determine the true identity and inner of the object SafeComHolder identity; - hr = InteropLib::Com::GetIdentityForCreateWrapperForExternal( + hr = InteropLib::Com::DetermineIdentityAndInnerForExternal( externalComObject, (CreateObjectFlags)flags, - &identity); + &identity, + &inner); _ASSERTE(hr == S_OK); - // Customized inners are only supported in aggregation with - // IReferenceTracker scenarios (e.g. WinRT). - IUnknown* inner = NULL; - if ((externalComObject != identity) - && (flags & CreateObjectFlags::CreateObjectFlags_TrackerObject) - && (flags & CreateObjectFlags::CreateObjectFlags_Aggregated)) - { - inner = externalComObject; - } - // Switch to Cooperative mode since object references // are being manipulated. { @@ -1480,6 +1473,13 @@ bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( _In_ OBJECTREF instance, _Outptr_ void** wrapperRaw) { + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; @@ -1506,6 +1506,13 @@ bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( _In_ INT32 objFromComIPFlags, _Out_ OBJECTREF* objRef) { + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + } + CONTRACTL_END; + if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; @@ -1523,7 +1530,8 @@ bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( { GCX_COOP(); - int flags = CreateObjectFlags::CreateObjectFlags_TrackerObject; + // TrackerObject support and unwrapping matches the built-in semantics that the global marshalling scenario mimics. + int flags = CreateObjectFlags::CreateObjectFlags_TrackerObject | CreateObjectFlags::CreateObjectFlags_Unwrap; if ((objFromComIPFlags & ObjFromComIP::UNIQUE_OBJECT) != 0) flags |= CreateObjectFlags::CreateObjectFlags_UniqueInstance; diff --git a/src/coreclr/vm/interoplibinterface.h b/src/coreclr/vm/interoplibinterface.h index 3d132a18ad6ef..16f8273f1aa1d 100644 --- a/src/coreclr/vm/interoplibinterface.h +++ b/src/coreclr/vm/interoplibinterface.h @@ -30,6 +30,7 @@ class ComWrappersNative _In_ QCall::ObjectHandleOnStack comWrappersImpl, _In_ INT64 wrapperId, _In_ void* externalComObject, + _In_opt_ void* innerMaybe, _In_ INT32 flags, _In_ QCall::ObjectHandleOnStack wrapperMaybe, _Inout_ QCall::ObjectHandleOnStack retValue); diff --git a/src/coreclr/vm/jithelpers.cpp b/src/coreclr/vm/jithelpers.cpp index 54d2dd04372b6..fe555c1427eee 100644 --- a/src/coreclr/vm/jithelpers.cpp +++ b/src/coreclr/vm/jithelpers.cpp @@ -57,6 +57,10 @@ #include "onstackreplacement.h" #include "pgo.h" +#ifndef FEATURE_EH_FUNCLETS +#include "excep.h" +#endif + //======================================================================== // // This file contains implementation of all JIT helpers. The helpers are @@ -2233,7 +2237,7 @@ HCIMPL2(Object*, IsInstanceOfAny_NoCacheLookup, CORINFO_CLASS_HANDLE type, Objec HELPER_METHOD_FRAME_BEGIN_RET_1(oref); if (!ObjIsInstanceOfCore(OBJECTREFToObject(oref), clsHnd)) oref = NULL; - HELPER_METHOD_POLL(); + HELPER_METHOD_POLL(); HELPER_METHOD_FRAME_END(); return OBJECTREFToObject(oref); @@ -2821,7 +2825,7 @@ NOINLINE HCIMPL3(VOID, JIT_Unbox_Nullable_Framed, void * destPtr, MethodTable* t { COMPlusThrowInvalidCastException(&objRef, TypeHandle(typeMT)); } - HELPER_METHOD_POLL(); + HELPER_METHOD_POLL(); HELPER_METHOD_FRAME_END(); } HCIMPLEND @@ -2862,7 +2866,7 @@ NOINLINE HCIMPL2(LPVOID, Unbox_Helper_Framed, MethodTable* pMT1, Object* obj) OBJECTREF objRef = ObjectToOBJECTREF(obj); HELPER_METHOD_FRAME_BEGIN_RET_1(objRef); - HELPER_METHOD_POLL(); + HELPER_METHOD_POLL(); if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() && (pMT1->IsEnum() || pMT1->IsTruePrimitive()) && @@ -5029,18 +5033,18 @@ void JIT_Patchpoint(int* counter, int ilOffset) ppId, ip, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset)); return; } - + // See if we have an OSR method for this patchpoint. PCODE osrMethodCode = ppInfo->m_osrMethodCode; bool isNewMethod = false; - + if (osrMethodCode == NULL) { // No OSR method yet, let's see if we should create one. // // First, optionally ignore some patchpoints to increase // coverage (stress mode). - // + // // Because there are multiple patchpoints in a method, and // each OSR method covers the remainder of the method from // that point until the method returns, if we trigger on an @@ -5050,7 +5054,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) #ifdef _DEBUG const int lowId = g_pConfig->OSR_LowId(); const int highId = g_pConfig->OSR_HighId(); - + if ((ppId < lowId) || (ppId > highId)) { LOG((LF_TIEREDCOMPILATION, LL_INFO10, "Jit_Patchpoint: ignoring patchpoint [%d] (0x%p) in Method=0x%pM (%s::%s) at offset %d\n", @@ -5092,13 +5096,13 @@ void JIT_Patchpoint(int* counter, int ilOffset) LOG((LF_TIEREDCOMPILATION, hitLogLevel, "Jit_Patchpoint: patchpoint [%d] (0x%p) hit %d in Method=0x%pM (%s::%s) [il offset %d] (limit %d)\n", ppId, ip, hitCount, pMD, pMD->m_pszDebugClassName, pMD->m_pszDebugMethodName, ilOffset, hitLimit)); - - // Defer, if we haven't yet reached the limit + + // Defer, if we haven't yet reached the limit if (hitCount < hitLimit) { return; } - + // Third, make sure no other thread is trying to create the OSR method. LONG oldFlags = ppInfo->m_flags; if ((oldFlags & PerPatchpointInfo::patchpoint_triggered) == PerPatchpointInfo::patchpoint_triggered) @@ -5106,16 +5110,16 @@ void JIT_Patchpoint(int* counter, int ilOffset) LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip)); return; } - + LONG newFlags = oldFlags | PerPatchpointInfo::patchpoint_triggered; BOOL triggerTransition = InterlockedCompareExchange(&ppInfo->m_flags, newFlags, oldFlags) == oldFlags; - + if (!triggerTransition) { LOG((LF_TIEREDCOMPILATION, LL_INFO1000, "Jit_Patchpoint: (lost race) AWAITING OSR method for patchpoint [%d] (0x%p)\n", ppId, ip)); return; } - + // Time to create the OSR method. // // We currently do this synchronously. We could instead queue @@ -5131,21 +5135,21 @@ void JIT_Patchpoint(int* counter, int ilOffset) // In this prototype we want to expose bugs in the jitted code // for OSR methods, so we stick with synchronous creation. LOG((LF_TIEREDCOMPILATION, LL_INFO10, "Jit_Patchpoint: patchpoint [%d] (0x%p) TRIGGER at count %d\n", ppId, ip, hitCount)); - + // Invoke the helper to build the OSR method osrMethodCode = HCCALL3(JIT_Patchpoint_Framed, pMD, codeInfo, ilOffset); - + // If that failed, mark the patchpoint as invalid. if (osrMethodCode == NULL) { // Unexpected, but not fatal STRESS_LOG3(LF_TIEREDCOMPILATION, LL_WARNING, "Jit_Patchpoint: patchpoint (0x%p) OSR method creation failed," " marking patchpoint invalid for Method=0x%pM il offset %d\n", ip, pMD, ilOffset); - + InterlockedOr(&ppInfo->m_flags, (LONG)PerPatchpointInfo::patchpoint_invalid); return; } - + // We've successfully created the osr method; make it available. _ASSERTE(ppInfo->m_osrMethodCode == NULL); ppInfo->m_osrMethodCode = osrMethodCode; @@ -5156,26 +5160,26 @@ void JIT_Patchpoint(int* counter, int ilOffset) _ASSERTE(osrMethodCode != NULL); 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(); #endif - + // Find context for the original method CONTEXT frameContext; frameContext.ContextFlags = CONTEXT_FULL; RtlCaptureContext(&frameContext); - + // Walk back to the original method frame pThread->VirtualUnwindToFirstManagedCallFrame(&frameContext); - + // Remember original method FP and SP because new method will inherit them. UINT_PTR currentSP = GetSP(&frameContext); UINT_PTR currentFP = GetFP(&frameContext); - + // We expect to be back at the right IP if ((UINT_PTR)ip != GetIP(&frameContext)) { @@ -5184,31 +5188,31 @@ void JIT_Patchpoint(int* counter, int ilOffset) " unexpected context IP 0x%p\n", ip, GetIP(&frameContext)); EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); } - + // Now unwind back to the original method caller frame. EECodeInfo callerCodeInfo(GetIP(&frameContext)); frameContext.ContextFlags = CONTEXT_FULL; ULONG_PTR establisherFrame = 0; PVOID handlerData = NULL; - RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(&frameContext), callerCodeInfo.GetFunctionEntry(), + RtlVirtualUnwind(UNW_FLAG_NHANDLER, callerCodeInfo.GetModuleBase(), GetIP(&frameContext), callerCodeInfo.GetFunctionEntry(), &frameContext, &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. - + #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; #endif - + SetSP(&frameContext, currentSP); frameContext.Rbp = currentFP; - + // 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; @@ -5216,7 +5220,7 @@ void JIT_Patchpoint(int* counter, int ilOffset) // Install new entry point as IP SetIP(&frameContext, osrMethodCode); - + // Transition! RtlRestoreContext(&frameContext, NULL); } @@ -5283,13 +5287,13 @@ HCIMPL2(void, JIT_ClassProfile, Object *obj, void* tableAddress) // access w/o tearing state. // static volatile unsigned s_rng = 100; - + unsigned x = s_rng; x ^= x << 13; x ^= x >> 17; x ^= x << 5; s_rng = x; - + // N is the sampling window size, // it should be larger than the table size. // @@ -5351,8 +5355,12 @@ EXTERN_C void JIT_PInvokeEnd(InlinedCallFrame* pFrame); // Forward declaration EXTERN_C void STDCALL ReversePInvokeBadTransition(); +#ifndef FEATURE_EH_FUNCLETS +EXCEPTION_HANDLER_DECL(FastNExportExceptHandler); +#endif + // This is a slower version of the reverse PInvoke enter function. -NOINLINE static void JIT_ReversePInvokeEnterRare(ReversePInvokeFrame* frame) +NOINLINE static void JIT_ReversePInvokeEnterRare(ReversePInvokeFrame* frame, void* traceAddr) { _ASSERTE(frame != NULL); @@ -5366,17 +5374,96 @@ NOINLINE static void JIT_ReversePInvokeEnterRare(ReversePInvokeFrame* frame) frame->currentThread = thread; +#ifdef PROFILING_SUPPORTED + if (CORProfilerTrackTransitions()) + { + ProfilerUnmanagedToManagedTransitionMD(frame->pMD, COR_PRF_TRANSITION_CALL); + } +#endif + thread->DisablePreemptiveGC(); +#ifdef DEBUGGING_SUPPORTED + // If the debugger is attached, we use this opportunity to see if + // we're disabling preemptive GC on the way into the runtime from + // unmanaged code. We end up here because + // Increment/DecrementTraceCallCount() will bump + // g_TrapReturningThreads for us. + if (CORDebuggerTraceCall()) + g_pDebugInterface->TraceCall((const BYTE*)traceAddr); +#endif // DEBUGGING_SUPPORTED } -NOINLINE static void JIT_ReversePInvokeEnterRare2(ReversePInvokeFrame* frame) +NOINLINE static void JIT_ReversePInvokeEnterRare2(ReversePInvokeFrame* frame, void* traceAddr) { frame->currentThread->RareDisablePreemptiveGC(); +#ifdef DEBUGGING_SUPPORTED + // If the debugger is attached, we use this opportunity to see if + // we're disabling preemptive GC on the way into the runtime from + // unmanaged code. We end up here because + // Increment/DecrementTraceCallCount() will bump + // g_TrapReturningThreads for us. + if (CORDebuggerTraceCall()) + g_pDebugInterface->TraceCall((const BYTE*)traceAddr); +#endif // DEBUGGING_SUPPORTED +} + +// The following JIT_ReversePInvoke helpers are special. +// They handle setting up Reverse P/Invoke calls and transitioning back to unmanaged code. +// As a result, we may not have a thread in JIT_ReversePInvokeEnter and we will be in the wrong GC mode for the HCALL prolog. +// Additionally, we set up and tear down SEH handlers when we're on x86, so we can't use dynamic contracts anyway. +// As a result, we specially decorate this method to have the correct calling convention +// and argument ordering for an HCALL, but we don't use the HCALL macros and contracts +// since this method doesn't follow the contracts. +void F_CALL_CONV HCCALL3(JIT_ReversePInvokeEnterTrackTransitions, ReversePInvokeFrame* frame, CORINFO_METHOD_HANDLE handle, void* secretArg) +{ + _ASSERTE(frame != NULL && handle != NULL); + + MethodDesc* pMD = GetMethod(handle); + if (pMD->IsILStub() && secretArg != NULL) + { + pMD = ((UMEntryThunk*)secretArg)->GetMethod(); + } + frame->pMD = pMD; + + Thread* thread = GetThreadNULLOk(); + + // If a thread instance exists and is in the + // correct GC mode attempt a quick transition. + if (thread != NULL + && !thread->PreemptiveGCDisabled()) + { + frame->currentThread = thread; + +#ifdef PROFILING_SUPPORTED + if (CORProfilerTrackTransitions()) + { + ProfilerUnmanagedToManagedTransitionMD(frame->pMD, COR_PRF_TRANSITION_CALL); + } +#endif + + // Manually inline the fast path in Thread::DisablePreemptiveGC(). + thread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(1); + if (g_TrapReturningThreads.LoadWithoutBarrier() != 0) + { + JIT_ReversePInvokeEnterRare2(frame, _ReturnAddress()); + } + } + else + { + JIT_ReversePInvokeEnterRare(frame, _ReturnAddress()); + } + +#ifndef FEATURE_EH_FUNCLETS + frame->record.m_pEntryFrame = frame->currentThread->GetFrame(); + frame->record.m_ExReg.Handler = (PEXCEPTION_ROUTINE)FastNExportExceptHandler; + INSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#endif } -EXTERN_C void JIT_ReversePInvokeEnter(ReversePInvokeFrame* frame) +void F_CALL_CONV HCCALL1(JIT_ReversePInvokeEnter, ReversePInvokeFrame* frame) { _ASSERTE(frame != NULL); + Thread* thread = GetThreadNULLOk(); // If a thread instance exists and is in the @@ -5388,19 +5475,24 @@ EXTERN_C void JIT_ReversePInvokeEnter(ReversePInvokeFrame* frame) // Manually inline the fast path in Thread::DisablePreemptiveGC(). thread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(1); - if (g_TrapReturningThreads.LoadWithoutBarrier() == 0) + if (g_TrapReturningThreads.LoadWithoutBarrier() != 0) { - return; + JIT_ReversePInvokeEnterRare2(frame, _ReturnAddress()); } - - JIT_ReversePInvokeEnterRare2(frame); - return; + } + else + { + JIT_ReversePInvokeEnterRare(frame, _ReturnAddress()); } - JIT_ReversePInvokeEnterRare(frame); +#ifndef FEATURE_EH_FUNCLETS + frame->record.m_pEntryFrame = frame->currentThread->GetFrame(); + frame->record.m_ExReg.Handler = (PEXCEPTION_ROUTINE)FastNExportExceptHandler; + INSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#endif } -EXTERN_C void JIT_ReversePInvokeExit(ReversePInvokeFrame* frame) +void F_CALL_CONV HCCALL1(JIT_ReversePInvokeExitTrackTransitions, ReversePInvokeFrame* frame) { _ASSERTE(frame != NULL); _ASSERTE(frame->currentThread == GetThread()); @@ -5409,6 +5501,32 @@ EXTERN_C void JIT_ReversePInvokeExit(ReversePInvokeFrame* frame) // This is a trade off with GC suspend performance. We are opting // to make this exit faster. frame->currentThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(0); + +#ifndef FEATURE_EH_FUNCLETS + UNINSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#endif + +#ifdef PROFILING_SUPPORTED + if (CORProfilerTrackTransitions()) + { + ProfilerUnmanagedToManagedTransitionMD(frame->pMD, COR_PRF_TRANSITION_RETURN); + } +#endif +} + +void F_CALL_CONV HCCALL1(JIT_ReversePInvokeExit, ReversePInvokeFrame* frame) +{ + _ASSERTE(frame != NULL); + _ASSERTE(frame->currentThread == GetThread()); + + // Manually inline the fast path in Thread::EnablePreemptiveGC(). + // This is a trade off with GC suspend performance. We are opting + // to make this exit faster. + frame->currentThread->m_fPreemptiveGCDisabled.StoreWithoutBarrier(0); + +#ifndef FEATURE_EH_FUNCLETS + UNINSTALL_EXCEPTION_HANDLING_RECORD(&frame->record.m_ExReg); +#endif } //======================================================================== diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 7e63ef114bc89..e0e4f81e42ad2 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -9255,22 +9255,7 @@ void CEEInfo::getFunctionFixedEntryPoint(CORINFO_METHOD_HANDLE ftn, MethodDesc * pMD = GetMethod(ftn); pResult->accessType = IAT_VALUE; - -#if defined(TARGET_X86) && !defined(CROSSGEN_COMPILE) - // Deferring X86 support until a need is observed or - // time permits investigation into all the potential issues. - // https://github.com/dotnet/runtime/issues/33582 - if (pMD->HasUnmanagedCallersOnlyAttribute()) - { - pResult->addr = (void*)COMDelegate::ConvertToUnmanagedCallback(pMD); - } - else - { - pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(); - } -#else pResult->addr = (void*)pMD->GetMultiCallableAddrOfCode(); -#endif EE_TO_JIT_TRANSITION(); } @@ -9800,7 +9785,7 @@ namespace return CorInfoCallConvExtension::Fastcall; case IMAGE_CEE_CS_CALLCONV_UNMANAGED: { - CorUnmanagedCallingConvention callConvMaybe; + CorInfoCallConvExtension callConvMaybe; UINT errorResID; HRESULT hr = MetaSig::TryGetUnmanagedCallingConventionFromModOpt(mod, pSig, cbSig, &callConvMaybe, pSuppressGCTransition, &errorResID); @@ -9809,11 +9794,11 @@ namespace if (hr == S_OK) { - return (CorInfoCallConvExtension)callConvMaybe; + return callConvMaybe; } else { - return (CorInfoCallConvExtension)MetaSig::GetDefaultUnmanagedCallingConvention(); + return MetaSig::GetDefaultUnmanagedCallingConvention(); } } case IMAGE_CEE_CS_CALLCONV_NATIVEVARARG: @@ -9848,25 +9833,7 @@ namespace } PInvokeStaticSigInfo sigInfo(pMD, PInvokeStaticSigInfo::NO_THROW_ON_ERROR); - switch (sigInfo.GetCallConv()) - { - case pmCallConvCdecl: - return CorInfoCallConvExtension::C; - break; - case pmCallConvStdcall: - return CorInfoCallConvExtension::Stdcall; - break; - case pmCallConvThiscall: - return CorInfoCallConvExtension::Thiscall; - break; - case pmCallConvFastcall: - return CorInfoCallConvExtension::Fastcall; - break; - default: - _ASSERTE_MSG(false, "bad callconv"); - return CorInfoCallConvExtension::Managed; - break; - } + return sigInfo.GetCallConv(); } else { @@ -9874,36 +9841,16 @@ namespace _ASSERTE_MSG(false, "UnmanagedCallersOnly methods are not supported in crossgen and should be rejected before getting here."); return CorInfoCallConvExtension::Managed; #else - CorPinvokeMap unmanagedCallConv; + CorInfoCallConvExtension unmanagedCallConv; if (TryGetCallingConventionFromUnmanagedCallersOnly(pMD, &unmanagedCallConv)) { if (methodCallConv == IMAGE_CEE_CS_CALLCONV_VARARG) { return CorInfoCallConvExtension::C; } - switch (unmanagedCallConv) - { - case pmCallConvWinapi: - return (CorInfoCallConvExtension)MetaSig::GetDefaultUnmanagedCallingConvention(); - break; - case pmCallConvCdecl: - return CorInfoCallConvExtension::C; - break; - case pmCallConvStdcall: - return CorInfoCallConvExtension::Stdcall; - break; - case pmCallConvThiscall: - return CorInfoCallConvExtension::Thiscall; - break; - case pmCallConvFastcall: - return CorInfoCallConvExtension::Fastcall; - break; - default: - _ASSERTE_MSG(false, "bad callconv"); - break; - } + return unmanagedCallConv; } - return (CorInfoCallConvExtension)MetaSig::GetDefaultUnmanagedCallingConvention(); + return MetaSig::GetDefaultUnmanagedCallingConvention(); #endif // CROSSGEN_COMPILE } } @@ -10274,7 +10221,11 @@ void CEEInfo::getEEInfo(CORINFO_EE_INFO *pEEInfoOut) pEEInfoOut->offsetOfWrapperDelegateIndirectCell = OFFSETOF__DelegateObject__methodPtrAux; pEEInfoOut->sizeOfReversePInvokeFrame = TARGET_POINTER_SIZE * READYTORUN_ReversePInvokeTransitionFrameSizeInPointerUnits; + + // The following assert doesn't work in cross-bitness scenarios since the pointer size differs. +#if (defined(TARGET_64BIT) && defined(HOST_64BIT)) || (defined(TARGET_32BIT) && defined(HOST_32BIT)) _ASSERTE(sizeof(ReversePInvokeFrame) <= pEEInfoOut->sizeOfReversePInvokeFrame); +#endif pEEInfoOut->osPageSize = GetOsPageSize(); pEEInfoOut->maxUncheckedOffsetForNullObject = MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT; @@ -12716,7 +12667,6 @@ CorJitResult CallCompileMethodWithSEHWrapper(EEJitManager *jitMgr, } } -#if !defined(TARGET_X86) if (ftn->HasUnmanagedCallersOnlyAttribute()) { // If the stub was generated by the runtime, don't validate @@ -12727,8 +12677,11 @@ CorJitResult CallCompileMethodWithSEHWrapper(EEJitManager *jitMgr, COMDelegate::ThrowIfInvalidUnmanagedCallersOnlyUsage(ftn); flags.Set(CORJIT_FLAGS::CORJIT_FLAG_REVERSE_PINVOKE); + if (CORProfilerTrackTransitions()) + { + flags.Set(CORJIT_FLAGS::CORJIT_FLAG_TRACK_TRANSITIONS); + } } -#endif // !TARGET_X86 return flags; } diff --git a/src/coreclr/vm/method.hpp b/src/coreclr/vm/method.hpp index 330e9e9cea7b3..5bdea982a7591 100644 --- a/src/coreclr/vm/method.hpp +++ b/src/coreclr/vm/method.hpp @@ -2023,23 +2023,16 @@ class MethodDesc private: PCODE PrepareILBasedCode(PrepareCodeConfig* pConfig); -#ifdef FEATURE_TIERED_COMPILATION - PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldCountCalls); - PCODE JitCompileCode(PrepareCodeConfig* pConfig, bool shouldCountCalls); - PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, bool shouldCountCalls); - PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, bool shouldCountCalls, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); -#else - PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig); - PCODE JitCompileCode(PrepareCodeConfig* pConfig); - PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); - PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); -#endif + PCODE GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldTier); PCODE GetPrecompiledNgenCode(PrepareCodeConfig* pConfig); PCODE GetPrecompiledR2RCode(PrepareCodeConfig* pConfig); PCODE GetMulticoreJitCode(PrepareCodeConfig* pConfig, bool* pWasTier0Jit); COR_ILMETHOD_DECODER* GetAndVerifyILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory); COR_ILMETHOD_DECODER* GetAndVerifyMetadataILHeader(PrepareCodeConfig* pConfig, COR_ILMETHOD_DECODER* pIlDecoderMemory); COR_ILMETHOD_DECODER* GetAndVerifyNoMetadataILHeader(); + PCODE JitCompileCode(PrepareCodeConfig* pConfig); + PCODE JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry); + PCODE JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pLockEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags); #endif // DACCESS_COMPILE #ifdef HAVE_GCCOVER @@ -2138,6 +2131,20 @@ class PrepareCodeConfig #ifdef FEATURE_TIERED_COMPILATION public: + bool WasTieringDisabledBeforeJitting() const + { + WRAPPER_NO_CONTRACT; + return m_wasTieringDisabledBeforeJitting; + } + + void SetWasTieringDisabledBeforeJitting() + { + WRAPPER_NO_CONTRACT; + _ASSERTE(GetMethodDesc()->IsEligibleForTieredCompilation()); + + m_wasTieringDisabledBeforeJitting = true; + } + bool ShouldCountCalls() const { WRAPPER_NO_CONTRACT; @@ -2245,6 +2252,7 @@ class PrepareCodeConfig #ifdef FEATURE_TIERED_COMPILATION private: + bool m_wasTieringDisabledBeforeJitting; bool m_shouldCountCalls; #endif @@ -2750,7 +2758,10 @@ class DynamicMethodDesc : public StoredSigMethodDesc void SetNativeStackArgSize(WORD cbArgSize) { LIMITED_METHOD_CONTRACT; - _ASSERTE(IsILStub() && (cbArgSize % TARGET_POINTER_SIZE) == 0); + _ASSERTE(IsILStub()); +#if !defined(OSX_ARM64_ABI) + _ASSERTE((cbArgSize % TARGET_POINTER_SIZE) == 0); +#endif m_dwExtendedFlags = (m_dwExtendedFlags & ~nomdStackArgSize) | ((DWORD)cbArgSize << 16); } @@ -3230,13 +3241,13 @@ class NDirectMethodDesc : public MethodDesc #endif public: - void SetStackArgumentSize(WORD cbDstBuffer, CorPinvokeMap unmgdCallConv) + void SetStackArgumentSize(WORD cbDstBuffer, CorInfoCallConvExtension unmgdCallConv) { LIMITED_METHOD_CONTRACT; #if defined(TARGET_X86) // thiscall passes the this pointer in ECX - if (unmgdCallConv == pmCallConvThiscall) + if (unmgdCallConv == CorInfoCallConvExtension::Thiscall) { _ASSERTE(cbDstBuffer >= sizeof(SLOT)); cbDstBuffer -= sizeof(SLOT); diff --git a/src/coreclr/vm/mlinfo.cpp b/src/coreclr/vm/mlinfo.cpp index 1097bbea2a6ad..71788db9efd5d 100644 --- a/src/coreclr/vm/mlinfo.cpp +++ b/src/coreclr/vm/mlinfo.cpp @@ -2873,18 +2873,27 @@ void MarshalInfo::SetupArgumentSizes() } CONTRACTL_END; + const unsigned targetPointerSize = TARGET_POINTER_SIZE; + const bool pointerIsValueType = false; + const bool pointerIsFloatHfa = false; + _ASSERTE(targetPointerSize == StackElemSize(TARGET_POINTER_SIZE, pointerIsValueType, pointerIsFloatHfa)); + if (m_byref) { - m_nativeArgSize = StackElemSize(TARGET_POINTER_SIZE); + m_nativeArgSize = targetPointerSize; } else { - m_nativeArgSize = StackElemSize(GetNativeSize(m_type)); + const bool isValueType = IsValueClass(m_type); + const bool isFloatHfa = isValueType && (m_pMT->GetHFAType() == CORINFO_HFA_ELEM_FLOAT); + m_nativeArgSize = StackElemSize(GetNativeSize(m_type), isValueType, isFloatHfa); } #ifdef ENREGISTERED_PARAMTYPE_MAXSIZE if (m_nativeArgSize > ENREGISTERED_PARAMTYPE_MAXSIZE) - m_nativeArgSize = StackElemSize(TARGET_POINTER_SIZE); + { + m_nativeArgSize = targetPointerSize; + } #endif // ENREGISTERED_PARAMTYPE_MAXSIZE } @@ -2909,16 +2918,8 @@ UINT16 MarshalInfo::GetNativeSize(MarshalType mtype) if (nativeSize == VARIABLESIZE) { - switch (mtype) - { - case MARSHAL_TYPE_BLITTABLEVALUECLASS: - case MARSHAL_TYPE_VALUECLASS: - case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR: - return (UINT16) m_pMT->GetNativeSize(); - - default: - _ASSERTE(0); - } + _ASSERTE(IsValueClass(mtype)); + return (UINT16) m_pMT->GetNativeSize(); } return nativeSize; @@ -2945,6 +2946,20 @@ bool MarshalInfo::IsInOnly(MarshalType mtype) return ILMarshalerIsInOnly[mtype]; } +bool MarshalInfo::IsValueClass(MarshalType mtype) +{ + switch (mtype) + { + case MARSHAL_TYPE_BLITTABLEVALUECLASS: + case MARSHAL_TYPE_VALUECLASS: + case MARSHAL_TYPE_BLITTABLEVALUECLASSWITHCOPYCTOR: + return true; + + default: + return false; + } +} + OVERRIDEPROC MarshalInfo::GetArgumentOverrideProc(MarshalType mtype) { CONTRACTL diff --git a/src/coreclr/vm/mlinfo.h b/src/coreclr/vm/mlinfo.h index 792d078220f56..3124c8ce5cf7c 100644 --- a/src/coreclr/vm/mlinfo.h +++ b/src/coreclr/vm/mlinfo.h @@ -490,6 +490,7 @@ class MarshalInfo UINT16 GetNativeSize(MarshalType mtype); static bool IsInOnly(MarshalType mtype); + static bool IsValueClass(MarshalType mtype); static OVERRIDEPROC GetArgumentOverrideProc(MarshalType mtype); static RETURNOVERRIDEPROC GetReturnOverrideProc(MarshalType mtype); diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 4a13ad212c29b..67226184ea6e3 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -361,18 +361,32 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) STANDARD_VM_CONTRACT; PCODE pCode = NULL; + bool shouldTier = false; #if defined(FEATURE_TIERED_COMPILATION) - bool shouldCountCalls = pConfig->GetMethodDesc()->IsEligibleForTieredCompilation(); -#if !defined(TARGET_X86) - if (shouldCountCalls + shouldTier = pConfig->GetMethodDesc()->IsEligibleForTieredCompilation(); + // If the method is eligible for tiering but is being + // called from a Preemptive GC Mode thread or the method + // has the UnmanagedCallersOnlyAttribute then the Tiered Compilation + // should be disabled. + if (shouldTier && (pConfig->GetCallerGCMode() == CallerGCMode::Preemptive || (pConfig->GetCallerGCMode() == CallerGCMode::Unknown && HasUnmanagedCallersOnlyAttribute()))) { - shouldCountCalls = false; + NativeCodeVersion codeVersion = pConfig->GetCodeVersion(); + if (codeVersion.IsDefaultVersion()) + { + pConfig->GetMethodDesc()->GetLoaderAllocator()->GetCallCountingManager()->DisableCallCounting(codeVersion); + _ASSERTE(codeVersion.GetOptimizationTier() != NativeCodeVersion::OptimizationTier0); + } + else if (codeVersion.GetOptimizationTier() == NativeCodeVersion::OptimizationTier0) + { + codeVersion.SetOptimizationTier(NativeCodeVersion::OptimizationTierOptimized); + } + pConfig->SetWasTieringDisabledBeforeJitting(); + shouldTier = false; } -#endif -#endif +#endif // FEATURE_TIERED_COMPILATION if (pConfig->MayUsePrecompiledCode()) { @@ -407,11 +421,7 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) if (pCode == NULL) { -#ifdef FEATURE_TIERED_COMPILATION - pCode = GetPrecompiledCode(pConfig, shouldCountCalls); -#else - pCode = GetPrecompiledCode(pConfig); -#endif + pCode = GetPrecompiledCode(pConfig, shouldTier); } #ifdef FEATURE_PERFMAP @@ -424,12 +434,7 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) { LOG((LF_CLASSLOADER, LL_INFO1000000, " In PrepareILBasedCode, calling JitCompileCode\n")); - -#ifdef FEATURE_TIERED_COMPILATION - pCode = JitCompileCode(pConfig, shouldCountCalls); -#else - pCode = JitCompileCode(pConfig); -#endif + pCode = JitCompileCode(pConfig); } else { @@ -442,11 +447,7 @@ PCODE MethodDesc::PrepareILBasedCode(PrepareCodeConfig* pConfig) return pCode; } -#ifdef FEATURE_TIERED_COMPILATION -PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldCountCalls) -#else -PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig) -#endif +PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig, bool shouldTier) { STANDARD_VM_CONTRACT; PCODE pCode = NULL; @@ -475,7 +476,7 @@ PCODE MethodDesc::GetPrecompiledCode(PrepareCodeConfig* pConfig) pConfig->SetGeneratedOrLoadedNewCode(); #endif #ifdef FEATURE_TIERED_COMPILATION - if (shouldCountCalls) + if (shouldTier) { _ASSERTE(pConfig->GetCodeVersion().GetOptimizationTier() == NativeCodeVersion::OptimizationTier0); pConfig->SetShouldCountCalls(); @@ -714,11 +715,8 @@ COR_ILMETHOD_DECODER* MethodDesc::GetAndVerifyILHeader(PrepareCodeConfig* pConfi // // This function creates a DeadlockAware list of methods being jitted // which prevents us from trying to JIT the same method more that once. -#ifdef FEATURE_TIERED_COMPILATION -PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig, bool shouldCountCalls) -#else + PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig) -#endif { STANDARD_VM_CONTRACT; @@ -811,7 +809,7 @@ PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig) { #ifdef FEATURE_TIERED_COMPILATION // Finalize the optimization tier before SetNativeCode() is called - shouldCountCalls = wasTier0Jit && pConfig->FinalizeOptimizationTierForTier0Jit() && shouldCountCalls; + bool shouldCountCalls = wasTier0Jit && pConfig->FinalizeOptimizationTierForTier0Jit(); #endif if (pConfig->SetNativeCode(pCode, &pCode)) @@ -831,20 +829,12 @@ PCODE MethodDesc::JitCompileCode(PrepareCodeConfig* pConfig) } } -#ifdef FEATURE_TIERED_COMPILATION - return JitCompileCodeLockedEventWrapper(pConfig, pEntryLock, shouldCountCalls); -#else return JitCompileCodeLockedEventWrapper(pConfig, pEntryLock); -#endif } } } -#ifdef FEATURE_TIERED_COMPILATION -PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, bool shouldCountCalls) -#else PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry) -#endif { STANDARD_VM_CONTRACT; @@ -899,11 +889,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J TRACE_LEVEL_VERBOSE, CLR_JIT_KEYWORD)) { -#ifdef FEATURE_TIERED_COMPILATION - pCode = JitCompileCodeLocked(pConfig, pEntry, shouldCountCalls, &sizeOfCode, &flags); -#else pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags); -#endif } else { @@ -923,11 +909,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J &methodSignature); #endif -#ifdef FEATURE_TIERED_COMPILATION - pCode = JitCompileCodeLocked(pConfig, pEntry, shouldCountCalls, &sizeOfCode, &flags); -#else pCode = JitCompileCodeLocked(pConfig, pEntry, &sizeOfCode, &flags); -#endif // Interpretted methods skip this notification #ifdef FEATURE_INTERPRETER @@ -1017,11 +999,7 @@ PCODE MethodDesc::JitCompileCodeLockedEventWrapper(PrepareCodeConfig* pConfig, J return pCode; } -#ifdef FEATURE_TIERED_COMPILATION -PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, bool shouldCountCalls, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags) -#else PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEntry* pEntry, ULONG* pSizeOfCode, CORJIT_FLAGS* pFlags) -#endif { STANDARD_VM_CONTRACT; @@ -1034,23 +1012,6 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn COR_ILMETHOD_DECODER ilDecoderTemp; COR_ILMETHOD_DECODER *pilHeader = GetAndVerifyILHeader(pConfig, &ilDecoderTemp); *pFlags = pConfig->GetJitCompilationFlags(); - -#ifdef FEATURE_TIERED_COMPILATION - bool isTier0 = pFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0); - // If we've already opted-out of call counting, for example in the UnmangedCallersOnly case, - // switch to optimized code. - if (!shouldCountCalls) - { - pFlags->Clear(CORJIT_FLAGS::CORJIT_FLAG_TIER0); - pFlags->Clear(CORJIT_FLAGS::CORJIT_FLAG_TIER1); - - if (pConfig->GetMethodDesc()->IsEligibleForTieredCompilation()) - { - pConfig->SetJitSwitchedToOptimized(); - } - } -#endif - PCODE pOtherCode = NULL; EX_TRY @@ -1118,7 +1079,7 @@ PCODE MethodDesc::JitCompileCodeLocked(PrepareCodeConfig* pConfig, JitListLockEn #ifdef FEATURE_TIERED_COMPILATION // Finalize the optimization tier before SetNativeCode() is called - shouldCountCalls = isTier0 && pConfig->FinalizeOptimizationTierForTier0Jit() && shouldCountCalls; + bool shouldCountCalls = pFlags->IsSet(CORJIT_FLAGS::CORJIT_FLAG_TIER0) && pConfig->FinalizeOptimizationTierForTier0Jit(); #endif // Aside from rejit, performing a SetNativeCodeInterlocked at this point @@ -1197,6 +1158,7 @@ PrepareCodeConfig::PrepareCodeConfig(NativeCodeVersion codeVersion, BOOL needsMu m_generatedOrLoadedNewCode(false), #endif #ifdef FEATURE_TIERED_COMPILATION + m_wasTieringDisabledBeforeJitting(false), m_shouldCountCalls(false), #endif m_jitSwitchedToMinOpt(false), @@ -1284,7 +1246,7 @@ CORJIT_FLAGS PrepareCodeConfig::GetJitCompilationFlags() flags = pResolver->GetJitFlags(); } #ifdef FEATURE_TIERED_COMPILATION - flags.Add(TieredCompilationManager::GetJitFlags(m_nativeCodeVersion)); + flags.Add(TieredCompilationManager::GetJitFlags(this)); #endif return flags; } @@ -1462,7 +1424,7 @@ CORJIT_FLAGS VersionedPrepareCodeConfig::GetJitCompilationFlags() #endif #ifdef FEATURE_TIERED_COMPILATION - flags.Add(TieredCompilationManager::GetJitFlags(m_nativeCodeVersion)); + flags.Add(TieredCompilationManager::GetJitFlags(this)); #endif return flags; diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 7f607c937f20c..8c8910af9a7dc 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -878,6 +878,11 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig pConfig->SetProfilerRejectedPrecompiledCode(); goto done; } + if (CORProfilerTrackTransitions() && pMD->HasUnmanagedCallersOnlyAttribute()) + { + pConfig->SetProfilerRejectedPrecompiledCode(); + goto done; + } #endif // PROFILING_SUPPORTED #endif // CROSSGEN_COMPILE diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index c4267ae151f36..3491dcb348944 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -828,7 +828,8 @@ FCIMPL5(Object*, RuntimeMethodHandle::InvokeMethod, CallDescrData callDescrData; callDescrData.pSrc = pTransitionBlock + sizeof(TransitionBlock); - callDescrData.numStackSlots = nStackBytes / STACK_ELEM_SIZE; + _ASSERTE((nStackBytes % TARGET_POINTER_SIZE) == 0); + callDescrData.numStackSlots = nStackBytes / TARGET_POINTER_SIZE; #ifdef CALLDESCR_ARGREGS callDescrData.pArgumentRegisters = (ArgumentRegisters*)(pTransitionBlock + TransitionBlock::GetOffsetOfArgumentRegisters()); #endif diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 4c76c62f2b882..142f462d73326 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -1716,22 +1716,7 @@ void * QCALLTYPE RuntimeMethodHandle::GetFunctionPointer(MethodDesc * pMethod) // Ensure the method is active so // the function pointer can be used. pMethod->EnsureActive(); - -#if defined(TARGET_X86) - // Deferring X86 support until a need is observed or - // time permits investigation into all the potential issues. - // https://github.com/dotnet/runtime/issues/33582 - if (pMethod->HasUnmanagedCallersOnlyAttribute()) - { - funcPtr = (void*)COMDelegate::ConvertToUnmanagedCallback(pMethod); - } - else - { - funcPtr = (void*)pMethod->GetMultiCallableAddrOfCode(); - } -#else funcPtr = (void*)pMethod->GetMultiCallableAddrOfCode(); -#endif END_QCALL; diff --git a/src/coreclr/vm/siginfo.cpp b/src/coreclr/vm/siginfo.cpp index 3f26bc8c87ac1..0193ed307b450 100644 --- a/src/coreclr/vm/siginfo.cpp +++ b/src/coreclr/vm/siginfo.cpp @@ -2598,7 +2598,7 @@ UINT MetaSig::GetElemSize(CorElementType etype, TypeHandle thValueType) // Returns size of that element in bytes. This is the minimum size that a // field of this type would occupy inside an object. // -UINT SigPointer::SizeOf(Module* pModule, const SigTypeContext *pTypeContext) const +UINT SigPointer::SizeOf(Module* pModule, const SigTypeContext *pTypeContext, TypeHandle* pTypeHandle) const { CONTRACTL { @@ -2613,9 +2613,8 @@ UINT SigPointer::SizeOf(Module* pModule, const SigTypeContext *pTypeContext) con } CONTRACTL_END - TypeHandle thValueType; - CorElementType etype = PeekElemTypeNormalized(pModule, pTypeContext, &thValueType); - return MetaSig::GetElemSize(etype, thValueType); + CorElementType etype = PeekElemTypeNormalized(pModule, pTypeContext, pTypeHandle); + return MetaSig::GetElemSize(etype, *pTypeHandle); } #ifndef DACCESS_COMPILE @@ -5321,7 +5320,7 @@ MetaSig::TryGetUnmanagedCallingConventionFromModOpt( _In_ CORINFO_MODULE_HANDLE pModule, _In_ PCCOR_SIGNATURE pSig, _In_ ULONG cSig, - _Out_ CorUnmanagedCallingConvention *callConvOut, + _Out_ CorInfoCallConvExtension *callConvOut, _Out_ bool* suppressGCTransitionOut, _Out_ UINT *errorResID) { @@ -5350,7 +5349,7 @@ MetaSig::TryGetUnmanagedCallingConventionFromModOpt( PCCOR_SIGNATURE pWalk = sigPtr.GetPtr(); _ASSERTE(pWalk <= pSig + cSig); - *callConvOut = (CorUnmanagedCallingConvention)0; + *callConvOut = CorInfoCallConvExtension::Managed; bool found = false; while ((pWalk < (pSig + cSig)) && ((*pWalk == ELEMENT_TYPE_CMOD_OPT) || (*pWalk == ELEMENT_TYPE_CMOD_REQD))) { @@ -5387,12 +5386,12 @@ MetaSig::TryGetUnmanagedCallingConventionFromModOpt( const struct { LPCSTR name; - CorUnmanagedCallingConvention value; + CorInfoCallConvExtension value; } knownCallConvs[] = { - { CMOD_CALLCONV_NAME_CDECL, IMAGE_CEE_UNMANAGED_CALLCONV_C }, - { CMOD_CALLCONV_NAME_STDCALL, IMAGE_CEE_UNMANAGED_CALLCONV_STDCALL }, - { CMOD_CALLCONV_NAME_THISCALL, IMAGE_CEE_UNMANAGED_CALLCONV_THISCALL }, - { CMOD_CALLCONV_NAME_FASTCALL, IMAGE_CEE_UNMANAGED_CALLCONV_FASTCALL } }; + { CMOD_CALLCONV_NAME_CDECL, CorInfoCallConvExtension::C }, + { CMOD_CALLCONV_NAME_STDCALL, CorInfoCallConvExtension::Stdcall }, + { CMOD_CALLCONV_NAME_THISCALL, CorInfoCallConvExtension::Thiscall }, + { CMOD_CALLCONV_NAME_FASTCALL, CorInfoCallConvExtension::Fastcall } }; for (const auto &callConv : knownCallConvs) { diff --git a/src/coreclr/vm/siginfo.hpp b/src/coreclr/vm/siginfo.hpp index 276a26bbe77fd..718604f71191c 100644 --- a/src/coreclr/vm/siginfo.hpp +++ b/src/coreclr/vm/siginfo.hpp @@ -197,7 +197,7 @@ class SigPointer : public SigParser // Returns size of that element in bytes. This is the minimum size that a // field of this type would occupy inside an object. //------------------------------------------------------------------------ - UINT SizeOf(Module* pModule, const SigTypeContext *pTypeContext) const; + UINT SizeOf(Module* pModule, const SigTypeContext *pTypeContext, TypeHandle* pTypeHandle) const; private: @@ -794,16 +794,16 @@ class MetaSig _In_ CORINFO_MODULE_HANDLE pModule, _In_ PCCOR_SIGNATURE pSig, _In_ ULONG cSig, - _Out_ CorUnmanagedCallingConvention *callConvOut, + _Out_ CorInfoCallConvExtension *callConvOut, _Out_ bool* suppressGCTransitionOut, _Out_ UINT *errorResID); - static CorUnmanagedCallingConvention GetDefaultUnmanagedCallingConvention() + static CorInfoCallConvExtension GetDefaultUnmanagedCallingConvention() { #ifdef TARGET_UNIX - return IMAGE_CEE_UNMANAGED_CALLCONV_C; + return CorInfoCallConvExtension::C; #else // TARGET_UNIX - return IMAGE_CEE_UNMANAGED_CALLCONV_STDCALL; + return CorInfoCallConvExtension::Stdcall; #endif // !TARGET_UNIX } @@ -856,7 +856,8 @@ class MetaSig { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; - return m_pRetType.SizeOf(m_pModule, &m_typeContext); + TypeHandle thValueType; + return m_pRetType.SizeOf(m_pModule, &m_typeContext, &thValueType); } //------------------------------------------------------------------ diff --git a/src/coreclr/vm/stubgen.cpp b/src/coreclr/vm/stubgen.cpp index 7016b8c2920a8..9077635906e5b 100644 --- a/src/coreclr/vm/stubgen.cpp +++ b/src/coreclr/vm/stubgen.cpp @@ -2763,6 +2763,73 @@ void ILStubLinker::SetStubTargetCallingConv(CorCallingConvention uNativeCallingC } } +void ILStubLinker::SetStubTargetCallingConv(CorInfoCallConvExtension callConv) +{ + LIMITED_METHOD_CONTRACT; + _ASSERTE(callConv != CorInfoCallConvExtension::Managed); + + const CorCallingConvention originalCallingConvention = m_nativeFnSigBuilder.GetCallingConv(); + if (originalCallingConvention != IMAGE_CEE_CS_CALLCONV_UNMANAGED) + { + // For performance reasons, we try to encode the calling convention using + // the flags-based method if possible before using modopts. + switch (callConv) + { + case CorInfoCallConvExtension::C: + m_nativeFnSigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_C); + break; + case CorInfoCallConvExtension::Stdcall: + m_nativeFnSigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_STDCALL); + break; + case CorInfoCallConvExtension::Thiscall: + m_nativeFnSigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_THISCALL); + break; + case CorInfoCallConvExtension::Fastcall: + m_nativeFnSigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_FASTCALL); + break; + default: + m_nativeFnSigBuilder.SetCallingConv(IMAGE_CEE_CS_CALLCONV_UNMANAGED); + break; + } + } + + // We may have updated the calling convention above, so reread the calling convention + // from the signature builder instead of using originalCallingConvention. + if (m_nativeFnSigBuilder.GetCallingConv() == IMAGE_CEE_CS_CALLCONV_UNMANAGED) + { + // In this case we're already using the "Unmanaged" calling convention, so encode the specific callconv + // with the requisite modopts for the calling convention. + switch (callConv) + { + case CorInfoCallConvExtension::C: + m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_CDECL))); + break; + case CorInfoCallConvExtension::Stdcall: + m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_STDCALL))); + break; + case CorInfoCallConvExtension::Thiscall: + m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_THISCALL))); + break; + case CorInfoCallConvExtension::Fastcall: + m_nativeFnSigBuilder.AddCallConvModOpt(GetToken(CoreLibBinder::GetClass(CLASS__CALLCONV_FASTCALL))); + break; + default: + _ASSERTE("Unknown calling convention. Unable to encode it in the native function pointer signature."); + break; + } + } + + if (!m_fIsReverseStub) + { + if (originalCallingConvention & CORINFO_CALLCONV_HASTHIS) + { + // Our calling convention had an implied-this beforehand and now it doesn't. + // Account for this in the target stack delta. + m_iTargetStackDelta++; + } + } +} + static size_t GetManagedTypeForMDArray(LocalDesc* pLoc, Module* pModule, PCCOR_SIGNATURE psigManagedArg, SigTypeContext *pTypeContext) { CONTRACTL diff --git a/src/coreclr/vm/stubgen.h b/src/coreclr/vm/stubgen.h index 18f6d609ca99c..a8b716063f23c 100644 --- a/src/coreclr/vm/stubgen.h +++ b/src/coreclr/vm/stubgen.h @@ -604,6 +604,7 @@ class ILStubLinker void SetStubTargetReturnType(CorElementType typ); void SetStubTargetReturnType(LocalDesc* pLoc); void SetStubTargetCallingConv(CorCallingConvention uNativeCallingConv); + void SetStubTargetCallingConv(CorInfoCallConvExtension callConv); bool ReturnOpcodePopsStack() { diff --git a/src/coreclr/vm/stubhelpers.cpp b/src/coreclr/vm/stubhelpers.cpp index 11f0af94b1efd..31692eee21db7 100644 --- a/src/coreclr/vm/stubhelpers.cpp +++ b/src/coreclr/vm/stubhelpers.cpp @@ -549,24 +549,6 @@ FCIMPL2(void*, StubHelpers::GetDelegateTarget, DelegateObject *pThisUNSAFE, UINT DELEGATEREF orefThis = (DELEGATEREF)ObjectToOBJECTREF(pThisUNSAFE); -#if defined(TARGET_X86) - // On x86 we wrap the call with a thunk that handles host notifications. - SyncBlock *pSyncBlock = orefThis->PassiveGetSyncBlock(); - if (pSyncBlock != NULL) - { - InteropSyncBlockInfo *pInteropInfo = pSyncBlock->GetInteropInfoNoCreate(); - if (pInteropInfo != NULL) - { - // we return entry point to a stub that wraps the real target - Stub *pInterceptStub = pInteropInfo->GetInterceptStub(); - if (pInterceptStub != NULL) - { - pEntryPoint = pInterceptStub->GetEntryPoint(); - } - } - } -#endif // TARGET_X86 - #if defined(HOST_64BIT) UINT_PTR target = (UINT_PTR)orefThis->GetMethodPtrAux(); diff --git a/src/coreclr/vm/syncblk.cpp b/src/coreclr/vm/syncblk.cpp index a362849a7525b..55736ec2cd4fa 100644 --- a/src/coreclr/vm/syncblk.cpp +++ b/src/coreclr/vm/syncblk.cpp @@ -71,7 +71,7 @@ InteropSyncBlockInfo::~InteropSyncBlockInfo() } CONTRACTL_END; - FreeUMEntryThunkOrInterceptStub(); + FreeUMEntryThunk(); } #ifndef TARGET_UNIX @@ -98,7 +98,7 @@ void InteropSyncBlockInfo::FlushStandbyList() } #endif // !TARGET_UNIX -void InteropSyncBlockInfo::FreeUMEntryThunkOrInterceptStub() +void InteropSyncBlockInfo::FreeUMEntryThunk() { CONTRACTL { @@ -117,22 +117,8 @@ void InteropSyncBlockInfo::FreeUMEntryThunkOrInterceptStub() COMDelegate::RemoveEntryFromFPtrHash((UPTR)pUMEntryThunk); UMEntryThunk::FreeUMEntryThunk((UMEntryThunk *)pUMEntryThunk); } - else - { -#if defined(TARGET_X86) - Stub *pInterceptStub = GetInterceptStub(); - if (pInterceptStub != NULL) - { - // There may be multiple chained stubs - pInterceptStub->DecRef(); - } -#else // TARGET_X86 - // Intercept stubs are currently not used on other platforms. - _ASSERTE(GetInterceptStub() == NULL); -#endif // TARGET_X86 - } } - m_pUMEntryThunkOrInterceptStub = NULL; + m_pUMEntryThunk = NULL; } #ifdef FEATURE_COMINTEROP diff --git a/src/coreclr/vm/syncblk.h b/src/coreclr/vm/syncblk.h index 91ff26eb5610b..01eddbcc2d9dd 100644 --- a/src/coreclr/vm/syncblk.h +++ b/src/coreclr/vm/syncblk.h @@ -729,50 +729,29 @@ class InteropSyncBlockInfo #endif // FEATURE_COMINTEROP #if !defined(DACCESS_COMPILE) - // set m_pUMEntryThunkOrInterceptStub if not already set - return true if not already set + // set m_pUMEntryThunk if not already set - return true if not already set bool SetUMEntryThunk(void* pUMEntryThunk) { WRAPPER_NO_CONTRACT; - return (FastInterlockCompareExchangePointer(&m_pUMEntryThunkOrInterceptStub, + return (FastInterlockCompareExchangePointer(&m_pUMEntryThunk, pUMEntryThunk, NULL) == NULL); } - // set m_pUMEntryThunkOrInterceptStub if not already set - return true if not already set - bool SetInterceptStub(Stub* pInterceptStub) - { - WRAPPER_NO_CONTRACT; - void *pPtr = (void *)((UINT_PTR)pInterceptStub | 1); - return (FastInterlockCompareExchangePointer(&m_pUMEntryThunkOrInterceptStub, - pPtr, - NULL) == NULL); - } - - void FreeUMEntryThunkOrInterceptStub(); + void FreeUMEntryThunk(); #endif // DACCESS_COMPILE void* GetUMEntryThunk() { LIMITED_METHOD_CONTRACT; - return (((UINT_PTR)m_pUMEntryThunkOrInterceptStub & 1) ? NULL : m_pUMEntryThunkOrInterceptStub); - } - - Stub* GetInterceptStub() - { - LIMITED_METHOD_CONTRACT; - return (((UINT_PTR)m_pUMEntryThunkOrInterceptStub & 1) ? (Stub *)((UINT_PTR)m_pUMEntryThunkOrInterceptStub & ~1) : NULL); + return m_pUMEntryThunk; } private: // If this is a delegate marshalled out to unmanaged code, this points // to the thunk generated for unmanaged code to call back on. - // If this is a delegate representing an unmanaged function pointer, - // this may point to a stub that intercepts calls to the unmng target. - // An example of an intercept call is pInvokeStackImbalance MDA. - // We differentiate between a thunk or intercept stub by setting the lowest - // bit if it is an intercept stub. - void* m_pUMEntryThunkOrInterceptStub; + void* m_pUMEntryThunk; #ifdef FEATURE_COMINTEROP // If this object is being exposed to COM, it will have an associated CCW object diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index ae99a64bb29d9..e26f002e06f04 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -102,7 +102,10 @@ thread_local int t_ForbidGCLoaderUseCount; uint64_t Thread::dead_threads_non_alloc_bytes = 0; SPTR_IMPL(ThreadStore, ThreadStore, s_pThreadStore); -CONTEXT *ThreadStore::s_pOSContext = NULL; + +CONTEXT* ThreadStore::s_pOSContext = NULL; +BYTE* ThreadStore::s_pOSContextBuffer = NULL; + CLREvent *ThreadStore::s_pWaitForStackCrawlEvent; PTR_ThreadLocalModule ThreadLocalBlock::GetTLMIfExists(ModuleIndex index) @@ -1468,7 +1471,6 @@ Thread::Thread() m_fHasDeadThreadBeenConsideredForGCTrigger = false; m_TraceCallCount = 0; m_ThrewControlForThread = 0; - m_OSContext = NULL; m_ThreadTasks = (ThreadTasks)0; m_pLoadLimiter= NULL; @@ -1501,7 +1503,11 @@ Thread::Thread() NewHolder contextHolder(m_OSContext); m_pSavedRedirectContext = NULL; - NewHolder savedRedirectContextHolder(m_pSavedRedirectContext); + m_pOSContextBuffer = NULL; + +#ifdef _DEBUG + m_RedirectContextInUse = false; +#endif #ifdef FEATURE_COMINTEROP m_pRCWStack = new RCWStackHeader(); @@ -1572,7 +1578,6 @@ Thread::Thread() trackSyncHolder.SuppressRelease(); #endif contextHolder.SuppressRelease(); - savedRedirectContextHolder.SuppressRelease(); #ifdef FEATURE_COMINTEROP m_uliInitializeSpyCookie.QuadPart = 0ul; @@ -2603,11 +2608,18 @@ Thread::~Thread() if (m_OSContext) delete m_OSContext; - if (GetSavedRedirectContext()) + if (m_pOSContextBuffer) { - delete GetSavedRedirectContext(); - SetSavedRedirectContext(NULL); + delete[] m_pOSContextBuffer; + m_pOSContextBuffer = NULL; } + else if (m_pSavedRedirectContext) + { + delete m_pSavedRedirectContext; + } + + MarkRedirectContextInUse(m_pSavedRedirectContext); + m_pSavedRedirectContext = NULL; #ifdef FEATURE_COMINTEROP if (m_pRCWStack) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index d8a355255121a..938667bcfbf8a 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -4003,8 +4003,21 @@ class Thread #endif // _DEBUG private: + // context used during redirection of this thread + // NOTE: there is only one. Since redirection cannot be nested + // if more than one are needed, something is wrong. PTR_CONTEXT m_pSavedRedirectContext; + // in a case when we need the redirection context to include CONTEXT_XSTATE + // this is the buffer that contains the context parts. + // we need the buffer so we could deallocate the whole deal. + BYTE* m_pOSContextBuffer; + +#ifdef _DEBUG + // validate that we use only one context per thread. + bool m_RedirectContextInUse; +#endif + BOOL IsContextSafeToRedirect(T_CONTEXT* pContext); public: @@ -4015,14 +4028,26 @@ class Thread } #ifndef DACCESS_COMPILE - void SetSavedRedirectContext(PT_CONTEXT pCtx) + void MarkRedirectContextInUse(PTR_CONTEXT pCtx) { LIMITED_METHOD_CONTRACT; - m_pSavedRedirectContext = pCtx; - } +#ifdef _DEBUG + _ASSERTE(!m_RedirectContextInUse); + _ASSERTE(pCtx == m_pSavedRedirectContext); + m_RedirectContextInUse = true; #endif + } - void EnsurePreallocatedContext(); + void UnmarkRedirectContextInUse(PTR_CONTEXT pCtx) + { + LIMITED_METHOD_CONTRACT; +#ifdef _DEBUG + _ASSERTE(m_RedirectContextInUse); + _ASSERTE(pCtx == m_pSavedRedirectContext); + m_RedirectContextInUse = false; +#endif + } +#endif //DACCESS_COMPILE ThreadLocalBlock m_ThreadLocalBlock; @@ -4854,12 +4879,21 @@ class ThreadStore #endif private: + static BYTE* s_pOSContextBuffer; static CONTEXT *s_pOSContext; public: - // We can not do any memory allocation after we suspend a thread in order ot - // avoid deadlock situation. + // Pre-allocate an OS context for possible use by a redirected thread and keep in a static variable. + // + // There are two reasons for this pattern: + // - We can not do any memory allocation after we suspend a thread in order to avoid deadlock situation. + // So, when anticipating a need, we must pre-allocate. + // + // - Even though we know the thread we are suspending, we do not want to put the context directly on the + // thread because the thread only _may_ need the context. Often it does not end up needing it, + // then we will keep the context for the next time like this. static void AllocateOSContext(); - static CONTEXT *GrabOSContext(); + // Retrieves and detaches the pre-alocated context + optional containing buffer (when CONTEXT_XSTATE is used) + static CONTEXT *GrabOSContext(BYTE** contextBuffer); private: // Thread abort needs to walk stack to decide if thread abort can proceed. diff --git a/src/coreclr/vm/threadsuspend.cpp b/src/coreclr/vm/threadsuspend.cpp index 6d987fa59cd3b..0d4a1605ab862 100644 --- a/src/coreclr/vm/threadsuspend.cpp +++ b/src/coreclr/vm/threadsuspend.cpp @@ -1962,39 +1962,109 @@ void ThreadSuspend::UnlockThreadStore(BOOL bThreadDestroyed, ThreadSuspend::SUSP #endif } +typedef BOOL(WINAPI* PINITIALIZECONTEXT2)(PVOID Buffer, DWORD ContextFlags, PCONTEXT* Context, PDWORD ContextLength, ULONG64 XStateCompactionMask); +PINITIALIZECONTEXT2 pfnInitializeContext2 = NULL; + +#ifdef TARGET_X86 +#define CONTEXT_COMPLETE (CONTEXT_FULL | CONTEXT_FLOATING_POINT | \ + CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS | CONTEXT_EXCEPTION_REQUEST) +#else +#define CONTEXT_COMPLETE (CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXCEPTION_REQUEST) +#endif + +CONTEXT* AllocateOSContextHelper(BYTE** contextBuffer) +{ + CONTEXT* pOSContext = NULL; + +#if !defined(TARGET_UNIX) && (defined(TARGET_X86) || defined(TARGET_AMD64)) + DWORD context = CONTEXT_COMPLETE; + BOOL supportsAVX = FALSE; + + if (pfnInitializeContext2 == NULL) + { + HMODULE hm = GetModuleHandleW(_T("kernel32.dll")); + pfnInitializeContext2 = (PINITIALIZECONTEXT2)GetProcAddress(hm, "InitializeContext2"); + } + + // Determine if the processor supports AVX so we could + // retrieve extended registers + DWORD64 FeatureMask = GetEnabledXStateFeatures(); + if ((FeatureMask & XSTATE_MASK_AVX) != 0) + { + context = context | CONTEXT_XSTATE; + supportsAVX = TRUE; + } + + // Retrieve contextSize by passing NULL for Buffer + DWORD contextSize = 0; + ULONG64 xStateCompactionMask = XSTATE_MASK_LEGACY | XSTATE_MASK_AVX; + // The initialize call should fail but return contextSize + BOOL success = pfnInitializeContext2 ? + pfnInitializeContext2(NULL, context, NULL, &contextSize, xStateCompactionMask) : + InitializeContext(NULL, context, NULL, &contextSize); + + _ASSERTE(!success && GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + // So now allocate a buffer of that size and call InitializeContext again + BYTE* buffer = new (nothrow)BYTE[contextSize]; + if (buffer != NULL) + { + success = pfnInitializeContext2 ? + pfnInitializeContext2(buffer, context, &pOSContext, &contextSize, xStateCompactionMask): + InitializeContext(buffer, context, &pOSContext, &contextSize); + + // if AVX is supported set the appropriate features mask in the context + if (success && supportsAVX) + { + // This should not normally fail. + // The system silently ignores any feature specified in the FeatureMask + // which is not enabled on the processor. + success = SetXStateFeaturesMask(pOSContext, XSTATE_MASK_AVX); + } + + if (!success) + { + delete[] buffer; + buffer = NULL; + } + } + + if (!success) + { + pOSContext = NULL; + } + + *contextBuffer = buffer; + +#else + pOSContext = new (nothrow) CONTEXT; + pOSContext->ContextFlags = CONTEXT_COMPLETE; + *contextBuffer = NULL; +#endif + + return pOSContext; +} void ThreadStore::AllocateOSContext() { LIMITED_METHOD_CONTRACT; _ASSERTE(HoldingThreadStore()); - if (s_pOSContext == NULL -#ifdef _DEBUG - || s_pOSContext == (CONTEXT*)0x1 -#endif - ) - { - s_pOSContext = new (nothrow) CONTEXT(); - } -#ifdef _DEBUG + if (s_pOSContext == NULL) { - s_pOSContext = (CONTEXT*)0x1; + s_pOSContext = AllocateOSContextHelper(&s_pOSContextBuffer); } -#endif } -CONTEXT *ThreadStore::GrabOSContext() +CONTEXT *ThreadStore::GrabOSContext(BYTE** contextBuffer) { LIMITED_METHOD_CONTRACT; _ASSERTE(HoldingThreadStore()); + CONTEXT *pContext = s_pOSContext; + *contextBuffer = s_pOSContextBuffer; s_pOSContext = NULL; -#ifdef _DEBUG - if (pContext == (CONTEXT*)0x1) - { - pContext = NULL; - } -#endif + s_pOSContextBuffer = NULL; return pContext; } @@ -2447,16 +2517,8 @@ void RedirectedThreadFrame::ExceptionUnwind() Thread* pThread = GetThread(); - if (pThread->GetSavedRedirectContext()) - { - delete m_Regs; - } - else - { - // Save it for future use to avoid repeatedly new'ing - pThread->SetSavedRedirectContext(m_Regs); - } - + // Allow future use to avoid repeatedly new'ing + pThread->UnmarkRedirectContextInUse(m_Regs); m_Regs = NULL; } @@ -2536,15 +2598,9 @@ int RedirectedHandledJITCaseExceptionFilter( ReplaceExceptionContextRecord(pExcepPtrs->ContextRecord, pCtx); DWORD espValue = pCtx->Esp; - if (pThread->GetSavedRedirectContext()) - { - delete pCtx; - } - else - { - // Save it for future use to avoid repeatedly new'ing - pThread->SetSavedRedirectContext(pCtx); - } + + // Allow future use to avoid repeatedly new'ing + pThread->UnmarkRedirectContextInUse(pCtx); ///////////////////////////////////////////////////////////////////////////// // NOTE: Ugly, ugly workaround. @@ -2637,9 +2693,8 @@ void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason) __try #endif // TARGET_X86 { - // Make sure this thread doesn't reuse the context memory in re-entrancy cases - _ASSERTE(pThread->GetSavedRedirectContext() != NULL); - pThread->SetSavedRedirectContext(NULL); + // Make sure this thread doesn't reuse the context memory. + pThread->MarkRedirectContextInUse(pCtx); // Link in the frame frame.Push(); @@ -2732,19 +2787,8 @@ void __stdcall Thread::RedirectedHandledJITCase(RedirectReason reason) frame.Pop(); { - // Free the context struct if we already have one cached - if (pThread->GetSavedRedirectContext()) - { - CONTEXT* pCtxTemp = (CONTEXT*)_alloca(sizeof(CONTEXT)); - memcpy(pCtxTemp, pCtx, sizeof(CONTEXT)); - delete pCtx; - pCtx = pCtxTemp; - } - else - { - // Save it for future use to avoid repeatedly new'ing - pThread->SetSavedRedirectContext(pCtx); - } + // Allow future use of the context + pThread->UnmarkRedirectContextInUse(pCtx); #if defined(HAVE_GCCOVER) && defined(USE_REDIRECT_FOR_GCSTRESS) // GCCOVER if (pThread->m_fPreemptiveGCDisabledForGCStress) @@ -2847,13 +2891,6 @@ void __stdcall Thread::RedirectedHandledJITCaseForGCStress() // own stack. // -#ifdef TARGET_X86 -#define CONTEXT_COMPLETE (CONTEXT_FULL | CONTEXT_FLOATING_POINT | \ - CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS | CONTEXT_EXCEPTION_REQUEST) -#else -#define CONTEXT_COMPLETE (CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXCEPTION_REQUEST) -#endif - BOOL Thread::RedirectThreadAtHandledJITCase(PFN_REDIRECTTARGET pTgt) { CONTRACTL { @@ -2864,56 +2901,29 @@ BOOL Thread::RedirectThreadAtHandledJITCase(PFN_REDIRECTTARGET pTgt) _ASSERTE(HandledJITCase()); _ASSERTE(GetThread() != this); + _ASSERTE(ThreadStore::HoldingThreadStore()); //////////////////////////////////////////////////////////////// // Acquire a context structure to save the thread state into - // We need to distinguish between two types of callers: - // - Most callers, including GC, operate while holding the ThreadStore + // All callers, including suspension, operate while holding the ThreadStore // lock. This means that we can pre-allocate a context structure // globally in the ThreadStore and use it in this function. - // - Some callers (currently only YieldTask) cannot take the ThreadStore - // lock. Therefore we always allocate a SavedRedirectContext in the - // Thread constructor. (Since YieldTask currently is the only caller - // that does not hold the ThreadStore lock, we only do this when - // we're hosted.) // Check whether we have a SavedRedirectContext we can reuse: CONTEXT *pCtx = GetSavedRedirectContext(); - // If we've never allocated a context for this thread, do so now + // If we've never assigned a context for this thread, do so now if (!pCtx) { - // If our caller took the ThreadStore lock, then it pre-allocated - // a context in the ThreadStore: - if (ThreadStore::HoldingThreadStore()) - { - pCtx = ThreadStore::GrabOSContext(); - } - - if (!pCtx) - { - // Even when our caller is YieldTask, we can find a NULL - // SavedRedirectContext in this function: Consider the scenario - // where GC is in progress and has already redirected a thread. - // That thread will set its SavedRedirectContext to NULL to enable - // reentrancy. Now assume that the host calls YieldTask for the - // redirected thread. In this case, this function will simply - // fail, but that is fine: The redirected thread will check, - // before it resumes execution, whether it should yield. - return (FALSE); - } - - // Save the pointer for the redirect function - _ASSERTE(GetSavedRedirectContext() == NULL); - SetSavedRedirectContext(pCtx); + pCtx = m_pSavedRedirectContext = ThreadStore::GrabOSContext(&m_pOSContextBuffer); + _ASSERTE(GetSavedRedirectContext() != NULL); } ////////////////////////////////////// // Get and save the thread's context - // Always get complete context - pCtx->ContextFlags = CONTEXT_COMPLETE; + // Always get complete context, pCtx->ContextFlags are set during Initialization BOOL bRes = EEGetThreadContext(this, pCtx); _ASSERTE(bRes && "Failed to GetThreadContext in RedirectThreadAtHandledJITCase - aborting redirect."); @@ -2987,9 +2997,6 @@ BOOL Thread::RedirectCurrentThreadAtHandledJITCase(PFN_REDIRECTTARGET pTgt, CONT } CONTRACTL_END; - // REVISIT_TODO need equivalent of this for the current thread - //_ASSERTE(HandledJITCase()); - _ASSERTE(GetThread() == this); _ASSERTE(PreemptiveGCDisabledOther()); _ASSERTE(IsAddrOfRedirectFunc(pTgt)); @@ -3004,27 +3011,17 @@ BOOL Thread::RedirectCurrentThreadAtHandledJITCase(PFN_REDIRECTTARGET pTgt, CONT // Check to see if we've already got memory allocated for this purpose. CONTEXT *pCtx = GetSavedRedirectContext(); - // If we've never allocated a context for this thread, do so now + // If we've never assigned a context for this thread, do so now if (!pCtx) { - pCtx = new (nothrow) CONTEXT(); - - if (!pCtx) - return (FALSE); - - // Save the pointer for the redirect function - _ASSERTE(GetSavedRedirectContext() == NULL); - SetSavedRedirectContext(pCtx); + pCtx = m_pSavedRedirectContext = AllocateOSContextHelper(&m_pOSContextBuffer); + _ASSERTE(GetSavedRedirectContext() != NULL); } ////////////////////////////////////// // Get and save the thread's context - - CopyMemory(pCtx, pCurrentThreadCtx, sizeof(CONTEXT)); - - // Clear any new bits we don't understand (like XSAVE) in case we pass - // this context to RtlRestoreContext (like for gcstress) - pCtx->ContextFlags &= CONTEXT_ALL; + BOOL success = CopyContext(pCtx, pCtx->ContextFlags, pCurrentThreadCtx); + _ASSERTE(success); // Ensure that this flag is set for the next time through the normal path, // RedirectThreadAtHandledJITCase. @@ -3407,11 +3404,6 @@ HRESULT ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason) continue; } - // We can not allocate memory after we suspend a thread. - // Otherwise, we may deadlock the process, because the thread we just suspended - // might hold locks we would need to acquire while allocating. - ThreadStore::AllocateOSContext(); - #ifdef TIME_SUSPEND DWORD startSuspend = g_SuspendStatistics.GetTime(); #endif @@ -3419,6 +3411,11 @@ HRESULT ThreadSuspend::SuspendRuntime(ThreadSuspend::SUSPEND_REASON reason) // // Suspend the native thread. // + + // We can not allocate memory after we suspend a thread. + // Otherwise, we may deadlock the process, because the thread we just suspended + // might hold locks we would need to acquire while allocating. + ThreadStore::AllocateOSContext(); Thread::SuspendThreadResult str = thread->SuspendThread(/*fOneTryOnly*/ TRUE); // We should just always build with this TIME_SUSPEND stuff, and report the results via ETW. @@ -4168,10 +4165,6 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) RetrySuspension: #endif // FEATURE_HIJACK && !TARGET_UNIX - // We can not allocate memory after we suspend a thread. - // Otherwise, we may deadlock the process when CLR is hosted. - ThreadStore::AllocateOSContext(); - #ifdef DISABLE_THREADSUSPEND // On platforms that do not support safe thread suspension we have // to rely on the GCPOLL mechanism. @@ -4185,6 +4178,9 @@ bool Thread::SysStartSuspendForDebug(AppDomain *pAppDomain) SuspendThreadResult str = STR_Success; FastInterlockOr(&thread->m_fPreemptiveGCDisabled, 0); #else + // We can not allocate memory after we suspend a thread. + // Otherwise, we may deadlock if suspended thread holds allocator locks. + ThreadStore::AllocateOSContext(); SuspendThreadResult str = thread->SuspendThread(); #endif // DISABLE_THREADSUSPEND @@ -4368,9 +4364,8 @@ bool Thread::SysSweepThreadsForDebug(bool forceSync) RetrySuspension: // We can not allocate memory after we suspend a thread. - // Otherwise, we may deadlock the process when CLR is hosted. + // Otherwise, we may deadlock if the suspended thread holds allocator locks. ThreadStore::AllocateOSContext(); - SuspendThreadResult str = thread->SuspendThread(); if (str == STR_Failure || str == STR_UnstartedOrDead) @@ -5917,7 +5912,6 @@ void HandleGCSuspensionForInterruptedThread(CONTEXT *interruptedContext) // If the thread is at a GC safe point, push a RedirectedThreadFrame with // the interrupted context and pulse the GC mode so that GC can proceed. FrameWithCookie frame(interruptedContext); - pThread->SetSavedRedirectContext(NULL); frame.Push(pThread); diff --git a/src/coreclr/vm/tieredcompilation.cpp b/src/coreclr/vm/tieredcompilation.cpp index 6b241de80b86c..9dcad74edd0b0 100644 --- a/src/coreclr/vm/tieredcompilation.cpp +++ b/src/coreclr/vm/tieredcompilation.cpp @@ -821,15 +821,9 @@ BOOL TieredCompilationManager::CompileCodeVersion(NativeCodeVersion nativeCodeVe PrepareCodeConfigBuffer configBuffer(nativeCodeVersion); PrepareCodeConfig *config = configBuffer.GetConfig(); -#if defined(TARGET_X86) - // Deferring X86 support until a need is observed or - // time permits investigation into all the potential issues. - // https://github.com/dotnet/runtime/issues/33582 -#else // This is a recompiling request which means the caller was // in COOP mode since the code already ran. _ASSERTE(!pMethod->HasUnmanagedCallersOnlyAttribute()); -#endif config->SetCallerGCMode(CallerGCMode::Coop); pCode = pMethod->PrepareCode(config); LOG((LF_TIEREDCOMPILATION, LL_INFO10000, "TieredCompilationManager::CompileCodeVersion Method=0x%pM (%s::%s), code version id=0x%x, code ptr=0x%p\n", @@ -928,30 +922,38 @@ NativeCodeVersion TieredCompilationManager::GetNextMethodToOptimize() } //static -CORJIT_FLAGS TieredCompilationManager::GetJitFlags(NativeCodeVersion nativeCodeVersion) +CORJIT_FLAGS TieredCompilationManager::GetJitFlags(PrepareCodeConfig *config) { - LIMITED_METHOD_CONTRACT; + WRAPPER_NO_CONTRACT; + _ASSERTE(config != nullptr); + _ASSERTE( + !config->WasTieringDisabledBeforeJitting() || + config->GetCodeVersion().GetOptimizationTier() != NativeCodeVersion::OptimizationTier0); CORJIT_FLAGS flags; - MethodDesc *methodDesc = nativeCodeVersion.GetMethodDesc(); - if (!methodDesc->IsEligibleForTieredCompilation()) - { -#ifdef FEATURE_INTERPRETER - flags.Set(CORJIT_FLAGS::CORJIT_FLAG_MAKEFINALCODE); -#endif - return flags; - } // Determine the optimization tier for the default code version (slightly faster common path during startup compared to // below), and disable call counting and set the optimization tier if it's not going to be tier 0 (this is used in other // places for the default code version where necessary to avoid the extra expense of GetOptimizationTier()). - if (nativeCodeVersion.IsDefaultVersion()) + NativeCodeVersion nativeCodeVersion = config->GetCodeVersion(); + if (nativeCodeVersion.IsDefaultVersion() && !config->WasTieringDisabledBeforeJitting()) { + MethodDesc *methodDesc = nativeCodeVersion.GetMethodDesc(); + if (!methodDesc->IsEligibleForTieredCompilation()) + { + _ASSERTE(nativeCodeVersion.GetOptimizationTier() == NativeCodeVersion::OptimizationTierOptimized); + #ifdef FEATURE_INTERPRETER + flags.Set(CORJIT_FLAGS::CORJIT_FLAG_MAKEFINALCODE); + #endif + return flags; + } + NativeCodeVersion::OptimizationTier newOptimizationTier; if (!methodDesc->RequestedAggressiveOptimization()) { if (g_pConfig->TieredCompilation_QuickJit()) { + _ASSERTE(nativeCodeVersion.GetOptimizationTier() == NativeCodeVersion::OptimizationTier0); flags.Set(CORJIT_FLAGS::CORJIT_FLAG_TIER0); return flags; } diff --git a/src/coreclr/vm/tieredcompilation.h b/src/coreclr/vm/tieredcompilation.h index 87ed9364172e0..003a9e558b21a 100644 --- a/src/coreclr/vm/tieredcompilation.h +++ b/src/coreclr/vm/tieredcompilation.h @@ -43,7 +43,7 @@ class TieredCompilationManager void HandleCallCountingForFirstCall(MethodDesc* pMethodDesc); bool TrySetCodeEntryPointAndRecordMethodForCallCounting(MethodDesc* pMethodDesc, PCODE codeEntryPoint); void AsyncPromoteToTier1(NativeCodeVersion tier0NativeCodeVersion, bool *scheduleTieringBackgroundWorkRef); - static CORJIT_FLAGS GetJitFlags(NativeCodeVersion nativeCodeVersion); + static CORJIT_FLAGS GetJitFlags(PrepareCodeConfig *config); private: bool IsTieringDelayActive(); diff --git a/src/coreclr/vm/typehandle.cpp b/src/coreclr/vm/typehandle.cpp index 8b494f07eb2d0..b198c87467c92 100644 --- a/src/coreclr/vm/typehandle.cpp +++ b/src/coreclr/vm/typehandle.cpp @@ -388,6 +388,7 @@ Instantiation TypeHandle::GetInstantiation() const BOOL TypeHandle::IsValueType() const { LIMITED_METHOD_DAC_CONTRACT; + _ASSERTE(!IsNull()); if (!IsTypeDesc()) return AsMethodTable()->IsValueType(); else return AsTypeDesc()->IsNativeValueType(); @@ -433,6 +434,16 @@ CorInfoHFAElemType TypeHandle::GetHFAType() const return CORINFO_HFA_ELEM_NONE; } +bool TypeHandle::IsFloatHfa() const +{ + WRAPPER_NO_CONTRACT; + if (IsNull() || !IsHFA()) + { + return false; + } + return (GetHFAType() == CORINFO_HFA_ELEM_FLOAT); +} + #ifdef FEATURE_64BIT_ALIGNMENT bool TypeHandle::RequiresAlign8() const diff --git a/src/coreclr/vm/typehandle.h b/src/coreclr/vm/typehandle.h index 387a75b362138..e623d4a8cec84 100644 --- a/src/coreclr/vm/typehandle.h +++ b/src/coreclr/vm/typehandle.h @@ -373,6 +373,8 @@ class TypeHandle bool IsHFA() const; CorInfoHFAElemType GetHFAType() const; + bool IsFloatHfa() const; + #ifdef FEATURE_64BIT_ALIGNMENT bool RequiresAlign8() const; #endif // FEATURE_64BIT_ALIGNMENT diff --git a/src/installer/Directory.Build.props b/src/installer/Directory.Build.props index 6506d42f9bb9e..7b51841c7f207 100644 --- a/src/installer/Directory.Build.props +++ b/src/installer/Directory.Build.props @@ -28,12 +28,6 @@ $(TargetArchitecture) - - false - false - false - - $(DefineConstants),DEBUG,TRACE diff --git a/src/installer/corehost/cli/apphost/static/CMakeLists.txt b/src/installer/corehost/cli/apphost/static/CMakeLists.txt index 6b4986ba310ea..78607e2eb7dae 100644 --- a/src/installer/corehost/cli/apphost/static/CMakeLists.txt +++ b/src/installer/corehost/cli/apphost/static/CMakeLists.txt @@ -33,7 +33,7 @@ set(HEADERS add_definitions(-D_NO_ASYNCRTIMP) add_definitions(-D_NO_PPLXIMP) -add_definitions(-DEXPORT_SHARED_API=1) +remove_definitions(-DEXPORT_SHARED_API) add_definitions(-DHOSTPOLICY_EMBEDDED) add_definitions(-DNATIVE_LIBS_EMBEDDED) @@ -50,9 +50,33 @@ if(CLR_CMAKE_TARGET_WIN32) ../apphost.windows.h) endif() +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) + else() + set(DEF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/singlefilehost_unixexports.src) + endif() + + set(EXPORTS_FILE ${CMAKE_CURRENT_BINARY_DIR}/singlefilehost.exports) + generate_exports_file(${DEF_SOURCES} ${EXPORTS_FILE}) + + set_exports_linker_option(${EXPORTS_FILE}) +endif() + include(../../exe.cmake) include(configure.cmake) +if(CLR_CMAKE_HOST_UNIX) + add_custom_target(singlefilehost_exports DEPENDS ${EXPORTS_FILE}) + add_dependencies(singlefilehost singlefilehost_exports) + + set_property(TARGET singlefilehost APPEND_STRING PROPERTY LINK_FLAGS ${EXPORTS_LINKER_OPTION}) + set_property(TARGET singlefilehost APPEND_STRING PROPERTY LINK_DEPENDS ${EXPORTS_FILE}) +endif() + add_definitions(-DFEATURE_APPHOST=1) add_definitions(-DFEATURE_STATIC_HOST=1) @@ -111,8 +135,8 @@ message ("Looking for coreclr_static lib at location: '${CORECLR_STATIC_LIB_LOCA if(CLR_CMAKE_TARGET_WIN32) set(NATIVE_LIBS ${CORECLR_STATIC_LIB_LOCATION}/coreclr_static.lib - ${CORECLR_STATIC_LIB_LOCATION}/System.Globalization.Native.lib - ${CORECLR_STATIC_LIB_LOCATION}/System.IO.Compression.Native.lib + ${CORECLR_STATIC_LIB_LOCATION}/System.Globalization.Native-Static.lib + ${CORECLR_STATIC_LIB_LOCATION}/System.IO.Compression.Native-Static.lib kernel32.lib advapi32.lib ole32.lib @@ -124,6 +148,9 @@ if(CLR_CMAKE_TARGET_WIN32) bcrypt.lib RuntimeObject.lib ) + + set(RUNTIMEINFO_LIB ${CORECLR_STATIC_LIB_LOCATION}/runtimeinfo.lib) + else() set(NATIVE_LIBS ${CORECLR_STATIC_LIB_LOCATION}/libcoreclr_static.a @@ -153,6 +180,9 @@ else() # Additional requirements for System.Security.Cryptography.Native.OpenSsl include(${CLR_REPO_ROOT_DIR}/src/libraries/Native/Unix/System.Security.Cryptography.Native/extra_libs.cmake) append_extra_cryptography_libs(NATIVE_LIBS) + + set(RUNTIMEINFO_LIB ${CORECLR_STATIC_LIB_LOCATION}/libruntimeinfo.a) + endif() if(CLR_CMAKE_TARGET_OSX) @@ -214,7 +244,25 @@ if(CLR_CMAKE_USE_SYSTEM_LIBUNWIND) ) endif() +if(CLR_CMAKE_TARGET_LINUX OR CLR_CMAKE_TARGET_FREEBSD OR CLR_CMAKE_TARGET_NETBSD OR CLR_CMAKE_TARGET_SUNOS) + # These options are used to force every object to be included even if it's unused. + set(START_WHOLE_ARCHIVE -Wl,--whole-archive) + set(END_WHOLE_ARCHIVE -Wl,--no-whole-archive) +endif(CLR_CMAKE_TARGET_LINUX OR CLR_CMAKE_TARGET_FREEBSD OR CLR_CMAKE_TARGET_NETBSD OR CLR_CMAKE_TARGET_SUNOS) + +if(CLR_CMAKE_TARGET_OSX) + # These options are used to force every object to be included even if it's unused. + set(START_WHOLE_ARCHIVE -force_load) + set(END_WHOLE_ARCHIVE ) +endif(CLR_CMAKE_TARGET_OSX) + +set_property(TARGET singlefilehost PROPERTY ENABLE_EXPORTS 1) + target_link_libraries( singlefilehost ${NATIVE_LIBS} + + ${START_WHOLE_ARCHIVE} + ${RUNTIMEINFO_LIB} + ${END_WHOLE_ARCHIVE} ) diff --git a/src/installer/corehost/cli/apphost/static/singlefilehost.def b/src/installer/corehost/cli/apphost/static/singlefilehost.def new file mode 100644 index 0000000000000..13f1405d9a8f0 --- /dev/null +++ b/src/installer/corehost/cli/apphost/static/singlefilehost.def @@ -0,0 +1,5 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. + +EXPORTS + DotNetRuntimeInfo diff --git a/src/installer/corehost/cli/apphost/static/singlefilehost_OSXexports.src b/src/installer/corehost/cli/apphost/static/singlefilehost_OSXexports.src new file mode 100644 index 0000000000000..98bc31e223e4e --- /dev/null +++ b/src/installer/corehost/cli/apphost/static/singlefilehost_OSXexports.src @@ -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. + +DotNetRuntimeInfo diff --git a/src/installer/corehost/cli/apphost/static/singlefilehost_unixexports.src b/src/installer/corehost/cli/apphost/static/singlefilehost_unixexports.src new file mode 100644 index 0000000000000..94ee037120d8d --- /dev/null +++ b/src/installer/corehost/cli/apphost/static/singlefilehost_unixexports.src @@ -0,0 +1,8 @@ +; Licensed to the .NET Foundation under one or more agreements. +; The .NET Foundation licenses this file to you under the MIT license. + +DotNetRuntimeInfo + +; FreeBSD needs to reexport these +__progname +environ diff --git a/src/installer/corehost/cli/hostmisc/pal.h b/src/installer/corehost/cli/hostmisc/pal.h index cdbb42a1a8def..42704f13fe5bd 100644 --- a/src/installer/corehost/cli/hostmisc/pal.h +++ b/src/installer/corehost/cli/hostmisc/pal.h @@ -107,7 +107,7 @@ namespace pal #ifdef EXPORT_SHARED_API #define SHARED_API extern "C" __declspec(dllexport) #else - #define SHARED_API + #define SHARED_API extern "C" #endif #define STDMETHODCALLTYPE __stdcall @@ -178,17 +178,13 @@ namespace pal #ifdef EXPORT_SHARED_API #define SHARED_API extern "C" __attribute__((__visibility__("default"))) #else - #define SHARED_API + #define SHARED_API extern "C" #endif #define __cdecl /* nothing */ #define __stdcall /* nothing */ #if !defined(TARGET_FREEBSD) #define __fastcall /* nothing */ - #else - #include - #include - #include #endif #define STDMETHODCALLTYPE __stdcall diff --git a/src/installer/corehost/cli/hostmisc/pal.unix.cpp b/src/installer/corehost/cli/hostmisc/pal.unix.cpp index 6883dcd07fc2e..f6f7e36fdf7ac 100644 --- a/src/installer/corehost/cli/hostmisc/pal.unix.cpp +++ b/src/installer/corehost/cli/hostmisc/pal.unix.cpp @@ -27,6 +27,10 @@ #include #elif defined(__sun) #include +#elif defined(TARGET_FREEBSD) +#include +#include +#include #endif #if !HAVE_DIRENT_D_TYPE diff --git a/src/installer/managed/Microsoft.NET.HostModel/ResourceUpdater.cs b/src/installer/managed/Microsoft.NET.HostModel/ResourceUpdater.cs index af71bfeea0a51..e18eedf9804d2 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/ResourceUpdater.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/ResourceUpdater.cs @@ -150,9 +150,9 @@ public static extern uint SizeofResource(IntPtr hModule, /// native resources for the update handle without updating /// the target file. /// - private class SafeUpdateHandle : SafeHandle + private sealed class SafeUpdateHandle : SafeHandle { - private SafeUpdateHandle() : base(IntPtr.Zero, true) + public SafeUpdateHandle() : base(IntPtr.Zero, true) { } diff --git a/src/installer/pkg/Directory.Build.targets b/src/installer/pkg/Directory.Build.targets index 0dd8ca63aef31..dbfa436544812 100644 --- a/src/installer/pkg/Directory.Build.targets +++ b/src/installer/pkg/Directory.Build.targets @@ -47,10 +47,10 @@ $(ProductBrandPrefix) Crossgen2 Pack - $(ProductBrandSuffix) $(ProductBrandPrefix) Runtime - $(ProductBrandSuffix) - com.microsoft.dotnet.sharedhost.component.osx.x64 - com.microsoft.dotnet.hostfxr.$(HostResolverVersion).component.osx.x64 - com.microsoft.dotnet.sharedframework.$(SharedFrameworkName).$(SharedFrameworkNugetVersion).component.osx.x64 - com.microsoft.dotnet.$(SharedFrameworkName).$(SharedFrameworkNugetVersion).osx.x64 + com.microsoft.dotnet.sharedhost.component.osx.$(TargetArchitecture) + com.microsoft.dotnet.hostfxr.$(HostResolverVersion).component.osx.$(TargetArchitecture) + com.microsoft.dotnet.sharedframework.$(SharedFrameworkName).$(SharedFrameworkNugetVersion).component.osx.$(TargetArchitecture) + com.microsoft.dotnet.$(SharedFrameworkName).$(SharedFrameworkNugetVersion).osx.$(TargetArchitecture) diff --git a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT index b8d644c4b6f5b..e756940d9035e 100644 --- a/src/installer/pkg/THIRD-PARTY-NOTICES.TXT +++ b/src/installer/pkg/THIRD-PARTY-NOTICES.TXT @@ -15,7 +15,7 @@ Copyright (c) .NET Foundation. All rights reserved. Licensed under the Apache License, Version 2.0. Available at -https://github.com/aspnet/AspNetCore/blob/master/LICENSE.txt +https://github.com/dotnet/aspnetcore/blob/master/LICENSE.txt License notice for Slicing-by-8 ------------------------------- @@ -116,12 +116,12 @@ 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 +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 International Organization for Standardization @@ -231,7 +231,7 @@ noted) — feel free to use them however you please. The aggregate collection an descriptions are © 1997-2005 Sean Eron Anderson. The code and descriptions are distributed in the hope that they will be useful, but WITHOUT ANY WARRANTY and without even the implied warranty of merchantability or fitness for a particular -purpose. +purpose. License notice for Brotli -------------------------------------- @@ -377,7 +377,7 @@ License notice for RFC 3492 --------------------------- The punycode implementation is based on the sample code in RFC 3492 - + Copyright (C) The Internet Society (2003). All Rights Reserved. This document and translations of it may be copied and furnished to @@ -516,8 +516,8 @@ License notice for Greg Parker ------------------------------ Greg Parker gparker@cs.stanford.edu December 2000 -This code is in the public domain and may be copied or modified without -permission. +This code is in the public domain and may be copied or modified without +permission. License notice for libunwind based code ---------------------------------------- @@ -547,23 +547,23 @@ License notice for Printing Floating-Point Numbers (Dragon4) /****************************************************************************** Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com/ - + This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. - + Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: - + 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. - + 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. - + 3. This notice may not be removed or altered from any source distribution. ******************************************************************************/ @@ -1138,17 +1138,17 @@ Copyright © Sven Groot (Ookii.org) 2009 All rights reserved. -Redistribution and use in source and binary forms, with or without +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. +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. + and/or other materials provided with the distribution. 3) Neither the name of the ORGANIZATION nor the names of its contributors may be used to endorse or promote products derived from this software - without specific prior written permission. + 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 diff --git a/src/installer/pkg/projects/netcoreappRIDs.props b/src/installer/pkg/projects/netcoreappRIDs.props index 1382ff46b822b..f3d784e9c82ec 100644 --- a/src/installer/pkg/projects/netcoreappRIDs.props +++ b/src/installer/pkg/projects/netcoreappRIDs.props @@ -5,6 +5,9 @@ + + arm64 + diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj index fee0cf0102bec..ec198224a2eb6 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Crossgen2.sfxproj @@ -9,7 +9,7 @@ Microsoft.NETCore.App.Crossgen2.$(RuntimeIdentifier) dotnet-crossgen2 crossgen2 - linux-x64;linux-musl-x64;osx-x64;win-x64 + linux-x64;linux-musl-x64;osx-x64;osx-arm64;win-x64 false AddRuntimeFilesToPackage; diff --git a/src/installer/pkg/sfx/Microsoft.NETCore.App/monocrossaot.sfxproj b/src/installer/pkg/sfx/Microsoft.NETCore.App/monocrossaot.sfxproj index 97ff1a1de517a..1e9216a3c0bd6 100644 --- a/src/installer/pkg/sfx/Microsoft.NETCore.App/monocrossaot.sfxproj +++ b/src/installer/pkg/sfx/Microsoft.NETCore.App/monocrossaot.sfxproj @@ -1,7 +1,11 @@ - android-x64;android-arm64;android-x86;android-arm + <_MonoCrossAOTTargetOS Condition="'$(MonoCrossAOTTargetOS)' != ''">+$(MonoCrossAOTTargetOS.ToLowerInvariant())+ + $(MonoAotTargets);android-x64;android-arm64;android-x86;android-arm + $(MonoAotTargets);browser-wasm + $(MonoAotTargets);tvos-x64;tvos-arm64 + $(MonoAotTargets);ios-x64;ios-arm64;ios-x86;ios-arm diff --git a/src/installer/pkg/sfx/bundle/Microsoft.NETCore.App.Bundle.bundleproj b/src/installer/pkg/sfx/bundle/Microsoft.NETCore.App.Bundle.bundleproj index d7ec1f72071e6..82eb623dd5e14 100644 --- a/src/installer/pkg/sfx/bundle/Microsoft.NETCore.App.Bundle.bundleproj +++ b/src/installer/pkg/sfx/bundle/Microsoft.NETCore.App.Bundle.bundleproj @@ -36,4 +36,12 @@ Properties="OutputPath=$(OutputPath)" /> + + + + + + diff --git a/src/installer/pkg/sfx/installers/dotnet-host.proj b/src/installer/pkg/sfx/installers/dotnet-host.proj index 29f7a3d6e3e47..6f645a73a5281 100644 --- a/src/installer/pkg/sfx/installers/dotnet-host.proj +++ b/src/installer/pkg/sfx/installers/dotnet-host.proj @@ -49,6 +49,18 @@ DestinationFiles="%(FilesToPublish.Destination)" /> + + + + + + + + diff --git a/src/installer/pkg/sfx/installers/dotnet-hostfxr.proj b/src/installer/pkg/sfx/installers/dotnet-hostfxr.proj index b7f7d17c93a42..f5992ef866a75 100644 --- a/src/installer/pkg/sfx/installers/dotnet-hostfxr.proj +++ b/src/installer/pkg/sfx/installers/dotnet-hostfxr.proj @@ -33,6 +33,18 @@ DestinationFolder="$(OutputPath)/host/fxr/$(Version)" /> + + + + + + + + diff --git a/src/installer/pkg/sfx/installers/osx_scripts/host/postinstall b/src/installer/pkg/sfx/installers/osx_scripts/host/postinstall old mode 100644 new mode 100755 diff --git a/src/installer/pkg/sfx/installers/osx_scripts/hostfxr/postinstall b/src/installer/pkg/sfx/installers/osx_scripts/hostfxr/postinstall old mode 100644 new mode 100755 diff --git a/src/installer/tests/Assets/TestUtils/TestProjects.targets b/src/installer/tests/Assets/TestUtils/TestProjects.targets index 175962ee44ea3..3ab4d72988c5a 100644 --- a/src/installer/tests/Assets/TestUtils/TestProjects.targets +++ b/src/installer/tests/Assets/TestUtils/TestProjects.targets @@ -17,7 +17,7 @@ LatestRuntimeFrameworkVersion="6.0.0-alpha.1.20602.4" RuntimeFrameworkName="Microsoft.NETCore.App" RuntimePackNamePatterns="Microsoft.NETCore.App.Runtime.**RID**" - RuntimePackRuntimeIdentifiers="linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86;ios-arm64;ios-arm;ios-x64;ios-x86;tvos-arm64;tvos-x64;android-arm64;android-arm;android-x64;android-x86;browser-wasm" + RuntimePackRuntimeIdentifiers="linux-arm;linux-arm64;linux-musl-arm64;linux-musl-x64;linux-x64;osx-x64;osx-arm64;rhel.6-x64;tizen.4.0.0-armel;tizen.5.0.0-armel;win-arm;win-arm64;win-x64;win-x86;ios-arm64;ios-arm;ios-x64;ios-x86;tvos-arm64;tvos-x64;android-arm64;android-arm;android-x64;android-x86;browser-wasm" TargetFramework="net6.0" TargetingPackName="Microsoft.NETCore.App.Ref" TargetingPackVersion="6.0.0-alpha.1.20602.4" /> @@ -25,7 +25,7 @@ - \ No newline at end of file + diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs index 9ec3cf2c96189..3c57e9f894291 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs @@ -37,7 +37,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFArrayHandle : SafeHandle { - private SafeCFArrayHandle() + public SafeCFArrayHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs index c703e23a9e0a1..68e8164a539f5 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFData.cs @@ -86,7 +86,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFDataHandle : SafeHandle { - internal SafeCFDataHandle() + public SafeCFDataHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs index fae8dfeefe309..4393796ef6add 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs @@ -49,7 +49,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFDateHandle : SafeHandle { - internal SafeCFDateHandle() + public SafeCFDateHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs index c85d65db50fdf..eeee71bd5a2a2 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs @@ -20,7 +20,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFDictionaryHandle : SafeHandle { - private SafeCFDictionaryHandle() + public SafeCFDictionaryHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFError.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFError.cs index db3d995f23b22..ad3b4de210938 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFError.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFError.cs @@ -52,7 +52,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFErrorHandle : SafeHandle { - internal SafeCFErrorHandle() + public SafeCFErrorHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs index 9bb891394e6a8..8c19957f55b7a 100644 --- a/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs +++ b/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs @@ -85,7 +85,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFStringHandle : SafeHandle { - internal SafeCFStringHandle() + public SafeCFStringHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Digest.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Digest.cs index a4f6e52ee9be5..b27ce0dd5b06a 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Digest.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Digest.cs @@ -42,7 +42,7 @@ namespace System.Security.Cryptography.Apple { internal sealed class SafeDigestCtxHandle : SafeHandle { - internal SafeDigestCtxHandle() + public SafeDigestCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs index 09bb04e86f78d..9898f5821289c 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Hmac.cs @@ -42,7 +42,7 @@ namespace System.Security.Cryptography.Apple { internal sealed class SafeHmacHandle : SafeHandle { - internal SafeHmacHandle() + public SafeHmacHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs index 36b3b7c15d2c6..fb7cb2bf7c871 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Keychain.cs @@ -292,7 +292,7 @@ namespace System.Security.Cryptography.Apple { internal class SafeKeychainItemHandle : SafeHandle { - internal SafeKeychainItemHandle() + public SafeKeychainItemHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -310,7 +310,7 @@ protected override bool ReleaseHandle() internal class SafeKeychainHandle : SafeHandle { - internal SafeKeychainHandle() + public SafeKeychainHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs index 577ee8126829a..4ff2379911065 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs @@ -412,7 +412,7 @@ namespace System.Net { internal sealed class SafeSslHandle : SafeHandle { - internal SafeSslHandle() + public SafeSslHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Symmetric.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Symmetric.cs index 2b55500b4ab36..22f6e512109ed 100644 --- a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Symmetric.cs +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Symmetric.cs @@ -83,7 +83,7 @@ internal static extern unsafe int CryptorFinal( namespace System.Security.Cryptography { - internal class SafeAppleCryptorHandle : SafeHandle + internal sealed class SafeAppleCryptorHandle : SafeHandle { public SafeAppleCryptorHandle() : base(IntPtr.Zero, ownsHandle: true) diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs index 5ca56e527be2d..ef439c56d845d 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OCSP.cs @@ -101,7 +101,7 @@ internal static SafeOcspRequestHandle X509ChainBuildOcspRequest(SafeX509StoreCtx namespace System.Security.Cryptography.X509Certificates { - internal class SafeOcspRequestHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeOcspRequestHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeOcspRequestHandle() : base(true) @@ -116,7 +116,7 @@ protected override bool ReleaseHandle() } } - internal class SafeOcspResponseHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeOcspResponseHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeOcspResponseHandle() : base(true) 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 459b9fef61f6c..0bbb4b056c605 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 @@ -368,7 +368,7 @@ private void Disconnect() } } - private SafeSslHandle() : base(IntPtr.Zero, true) + public SafeSslHandle() : base(IntPtr.Zero, true) { } 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 94f68c586129f..d39b6595d39b1 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 @@ -64,7 +64,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeSslContextHandle : SafeHandle { - private SafeSslContextHandle() + public SafeSslContextHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Name.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Name.cs index 929440435d4b1..6a0cf0c9e3d8e 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Name.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Name.cs @@ -49,7 +49,7 @@ namespace Microsoft.Win32.SafeHandles /// internal sealed class SafeSharedX509NameHandle : SafeInteriorHandle { - private SafeSharedX509NameHandle() : + public SafeSharedX509NameHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -61,7 +61,7 @@ private SafeSharedX509NameHandle() : /// internal sealed class SafeSharedX509NameStackHandle : SafeInteriorHandle { - private SafeSharedX509NameStackHandle() : + public SafeSharedX509NameStackHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs index ce84ac229d5bc..c3468042c0c69 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509Stack.cs @@ -63,7 +63,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeX509StackHandle : SafeHandle { - private SafeX509StackHandle() : + public SafeX509StackHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -100,7 +100,7 @@ internal sealed class SafeSharedX509StackHandle : SafeInteriorHandle { internal static readonly SafeSharedX509StackHandle InvalidHandle = new SafeSharedX509StackHandle(); - private SafeSharedX509StackHandle() : + public SafeSharedX509StackHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs index 38d8a3ae26bdb..e98faa6fe21db 100644 --- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs +++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.X509StoreCtx.cs @@ -75,7 +75,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeX509StoreCtxHandle : SafeHandle { - private SafeX509StoreCtxHandle() : + public SafeX509StoreCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeHashHandle.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeHashHandle.cs index 860e33716029e..3717976436dfb 100644 --- a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeHashHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeHashHandle.cs @@ -14,7 +14,7 @@ internal sealed class SafeHashHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeProvHandle? _parent; - private SafeHashHandle() : base(true) + public SafeHashHandle() : base(true) { SetHandle(IntPtr.Zero); } diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeKeyHandle.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeKeyHandle.cs index c41a5432a47e8..481499fe982ac 100644 --- a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeKeyHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeKeyHandle.cs @@ -23,7 +23,7 @@ internal sealed class SafeKeyHandle : SafeHandleZeroOrMinusOneIsInvalid private bool _fPublicOnly; private SafeProvHandle? _parent; - private SafeKeyHandle() : base(true) + public SafeKeyHandle() : base(true) { SetHandle(IntPtr.Zero); _keySpec = 0; diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeProvHandle.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeProvHandle.cs index 317d3a6c38b2e..3aa1e32e05143 100644 --- a/src/libraries/Common/src/Interop/Windows/Advapi32/SafeProvHandle.cs +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/SafeProvHandle.cs @@ -18,7 +18,7 @@ internal sealed class SafeProvHandle : SafeHandleZeroOrMinusOneIsInvalid private uint _flags; private bool _fPersistKeyInCsp; - private SafeProvHandle() : base(true) + public SafeProvHandle() : base(true) { SetHandle(IntPtr.Zero); _containerName = null; diff --git a/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ICMP.cs b/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ICMP.cs index 915e26af070da..1926b414e9bf1 100644 --- a/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ICMP.cs +++ b/src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ICMP.cs @@ -80,7 +80,7 @@ internal struct Icmp6EchoReply internal sealed class SafeCloseIcmpHandle : SafeHandleZeroOrMinusOneIsInvalid { - private SafeCloseIcmpHandle() : base(true) + public SafeCloseIcmpHandle() : base(true) { } diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index 5c14f4c69ffa6..ea1762564aaf3 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -148,7 +148,7 @@ internal sealed class SafeFreeCertContext : SafeHandleZeroOrMinusOneIsInvalid { #endif - internal SafeFreeCertContext() : base(true) { } + public SafeFreeCertContext() : base(true) { } // This must be ONLY called from this file. internal void Set(IntPtr value) @@ -1080,7 +1080,7 @@ internal static unsafe int ApplyControlToken( internal sealed class SafeDeleteSslContext : SafeDeleteContext { - internal SafeDeleteSslContext() : base() { } + public SafeDeleteSslContext() : base() { } protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Interop/Windows/WinSock/SafeNativeOverlapped.cs b/src/libraries/Common/src/Interop/Windows/WinSock/SafeNativeOverlapped.cs index 5a4a68a1d53ea..5d81d73a4fbe5 100644 --- a/src/libraries/Common/src/Interop/Windows/WinSock/SafeNativeOverlapped.cs +++ b/src/libraries/Common/src/Interop/Windows/WinSock/SafeNativeOverlapped.cs @@ -14,7 +14,7 @@ internal sealed class SafeNativeOverlapped : SafeHandle { private readonly SafeSocketHandle? _socketHandle; - private SafeNativeOverlapped() + public SafeNativeOverlapped() : this(IntPtr.Zero) { if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this); diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs index 7a5667ec8e646..193533551bcea 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/Asn1SafeHandles.Unix.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeAsn1ObjectHandle : SafeHandle { - private SafeAsn1ObjectHandle() : + public SafeAsn1ObjectHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -28,7 +28,7 @@ public override bool IsInvalid internal sealed class SafeAsn1BitStringHandle : SafeHandle { - private SafeAsn1BitStringHandle() : + public SafeAsn1BitStringHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -48,7 +48,7 @@ public override bool IsInvalid internal sealed class SafeAsn1OctetStringHandle : SafeHandle { - private SafeAsn1OctetStringHandle() : + public SafeAsn1OctetStringHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs index 79283d19ecb53..b9e3b644132d4 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/GssSafeHandles.cs @@ -61,7 +61,7 @@ protected override bool ReleaseHandle() return status == Interop.NetSecurityNative.Status.GSS_S_COMPLETE; } - private SafeGssNameHandle() + public SafeGssNameHandle() : base(IntPtr.Zero, true) { } @@ -70,7 +70,7 @@ private SafeGssNameHandle() /// /// Wrapper around a gss_cred_id_t_desc_struct* /// - internal class SafeGssCredHandle : SafeHandle + internal sealed class SafeGssCredHandle : SafeHandle { private static readonly Lazy s_IsNtlmInstalled = new Lazy(InitIsNtlmInstalled); @@ -132,7 +132,7 @@ public static SafeGssCredHandle Create(string username, string password, bool is return retHandle; } - private SafeGssCredHandle() + public SafeGssCredHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptAlgorithmHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptAlgorithmHandle.cs index d9192108d605f..ec4192dc35267 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptAlgorithmHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptAlgorithmHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeBCryptAlgorithmHandle : SafeBCryptHandle { - private SafeBCryptAlgorithmHandle() + public SafeBCryptAlgorithmHandle() : base() { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptHashHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptHashHandle.cs index e3d899c8e217e..b4426f9e5feed 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptHashHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptHashHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeBCryptHashHandle : SafeBCryptHandle { - private SafeBCryptHashHandle() + public SafeBCryptHashHandle() : base() { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptKeyHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptKeyHandle.cs index e7962332f219b..08fc455e140f7 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptKeyHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBCryptKeyHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeBCryptKeyHandle : SafeBCryptHandle { - private SafeBCryptKeyHandle() + public SafeBCryptKeyHandle() : base() { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBignumHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBignumHandle.Unix.cs index 311f24ca08630..9e702b45ff947 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBignumHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBignumHandle.Unix.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeBignumHandle : SafeHandle { - private SafeBignumHandle() : + public SafeBignumHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs index ee59dcd0eb720..db95a83addbab 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeBioHandle.Unix.cs @@ -13,7 +13,7 @@ internal sealed class SafeBioHandle : SafeHandle { private SafeHandle? _parent; - private SafeBioHandle() : + public SafeBioHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeCreateHandle.OSX.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeCreateHandle.OSX.cs index 63df101a9ba00..c7a3d25fa2acd 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeCreateHandle.OSX.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeCreateHandle.OSX.cs @@ -14,7 +14,7 @@ namespace Microsoft.Win32.SafeHandles /// internal sealed partial class SafeCreateHandle : SafeHandle { - internal SafeCreateHandle() : base(IntPtr.Zero, true) { } + public SafeCreateHandle() : base(IntPtr.Zero, true) { } internal SafeCreateHandle(IntPtr ptr) : base(IntPtr.Zero, true) { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeDsaHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeDsaHandle.Unix.cs index 7d177ea60f043..0899f2f1938b9 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeDsaHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeDsaHandle.Unix.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeDsaHandle : SafeHandle { - private SafeDsaHandle() : + public SafeDsaHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEcKeyHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEcKeyHandle.Unix.cs index ef001881ee323..cebd28b840fd6 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEcKeyHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEcKeyHandle.Unix.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEcKeyHandle : SafeHandle { - private SafeEcKeyHandle() : + public SafeEcKeyHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEventStreamHandle.OSX.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEventStreamHandle.OSX.cs index 622689b3e572c..a1d38ee7f99b4 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEventStreamHandle.OSX.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEventStreamHandle.OSX.cs @@ -15,7 +15,7 @@ namespace Microsoft.Win32.SafeHandles /// internal sealed partial class SafeEventStreamHandle : SafeHandle { - internal SafeEventStreamHandle() : base(IntPtr.Zero, true) { } + public SafeEventStreamHandle() : base(IntPtr.Zero, true) { } internal SafeEventStreamHandle(IntPtr ptr) : base(IntPtr.Zero, true) { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpCipherCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpCipherCtxHandle.Unix.cs index bc965e7cf16e0..f5b5cd3dca266 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpCipherCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpCipherCtxHandle.Unix.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEvpCipherCtxHandle : SafeHandle { - private SafeEvpCipherCtxHandle() : + public SafeEvpCipherCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpMdCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpMdCtxHandle.Unix.cs index a15f0644d1ba5..b1e0bfe74679c 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpMdCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpMdCtxHandle.Unix.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEvpMdCtxHandle : SafeHandle { - private SafeEvpMdCtxHandle() : + public SafeEvpMdCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPKeyHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPKeyHandle.Unix.cs index 99d699ea81a74..9e67a1a9df2d6 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPKeyHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPKeyHandle.Unix.cs @@ -15,7 +15,7 @@ sealed class SafeEvpPKeyHandle : SafeHandle { internal static readonly SafeEvpPKeyHandle InvalidHandle = new SafeEvpPKeyHandle(); - private SafeEvpPKeyHandle() : + public SafeEvpPKeyHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs index 2845f99073280..d64f135ca0f72 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeEvpPkeyCtxHandle.Unix.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEvpPKeyCtxHandle : SafeHandle { - private SafeEvpPKeyCtxHandle() + public SafeEvpPKeyCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs index 5d90bfff06c66..318dfd2c2d9f3 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeFindHandle : SafeHandle { - internal SafeFindHandle() : base(IntPtr.Zero, true) { } + public SafeFindHandle() : base(IntPtr.Zero, true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeHmacCtxHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeHmacCtxHandle.Unix.cs index f4284aa542994..5bb60d1295e17 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeHmacCtxHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeHmacCtxHandle.Unix.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeHmacCtxHandle : SafeHandle { - private SafeHmacCtxHandle() : + public SafeHmacCtxHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs index 4197d6834be25..f7a57d09f0db9 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLibraryHandle.cs @@ -5,7 +5,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLibraryHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeLibraryHandle() : base(true) { } + public SafeLibraryHandle() : base(true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLocalAllocHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLocalAllocHandle.cs index 96a93bfe9b8e8..bfd945f8286ad 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLocalAllocHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLocalAllocHandle.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLocalAllocHandle : SafeBuffer { - private SafeLocalAllocHandle() : base(true) { } + public SafeLocalAllocHandle() : base(true) { } internal static readonly SafeLocalAllocHandle Zero = new SafeLocalAllocHandle(); diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaHandle.cs index 86a89a616ea8b..ddf32d6359172 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLsaHandle : SafeHandle { - internal SafeLsaHandle() + public SafeLsaHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs index b744a4b46b70e..a97e68019182f 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLsaMemoryHandle : SafeBuffer { - private SafeLsaMemoryHandle() : base(true) { } + public SafeLsaMemoryHandle() : base(true) { } // 0 is an Invalid Handle internal SafeLsaMemoryHandle(IntPtr handle) : base(true) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs index 33263626622c7..ab1745f74da0b 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs @@ -7,7 +7,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLsaPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid { - private SafeLsaPolicyHandle() : base(true) { } + public SafeLsaPolicyHandle() : base(true) { } // 0 is an Invalid Handle internal SafeLsaPolicyHandle(IntPtr handle) : base(true) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs index b8ebb893c9ae1..195e37c346b47 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeLsaReturnBufferHandle : SafeBuffer { - private SafeLsaReturnBufferHandle() : base(true) { } + public SafeLsaReturnBufferHandle() : base(true) { } // 0 is an Invalid Handle internal SafeLsaReturnBufferHandle(IntPtr handle) : base(true) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePerfProviderHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePerfProviderHandle.cs index 4632fa14a93e7..afd51e1f43b16 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePerfProviderHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePerfProviderHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafePerfProviderHandle : SafeHandleZeroOrMinusOneIsInvalid { - private SafePerfProviderHandle() : base(true) { } + public SafePerfProviderHandle() : base(true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePkcs7Handle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePkcs7Handle.Unix.cs index 328db92c4f9cc..3326c3578d37d 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePkcs7Handle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafePkcs7Handle.Unix.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafePkcs7Handle : SafeHandle { - private SafePkcs7Handle() : + public SafePkcs7Handle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs index a379fed2e4079..aef516adb3a30 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeRsaHandle.Unix.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeRsaHandle : SafeHandle { - private SafeRsaHandle() : + public SafeRsaHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs index ce97df473c69f..d1e52e9a4f2c8 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs @@ -11,7 +11,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeTokenHandle : SafeHandle { - private SafeTokenHandle() : base(IntPtr.Zero, true) { } + public SafeTokenHandle() : base(IntPtr.Zero, true) { } // 0 is an Invalid Handle internal SafeTokenHandle(IntPtr handle) : base(IntPtr.Zero, true) diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs index 137c9d47aa38f..baced04d306d6 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeX509Handles.Unix.cs @@ -28,7 +28,7 @@ internal sealed class SafeX509Handle : SafeHandle internal static readonly SafeX509Handle InvalidHandle = new SafeX509Handle(); - private SafeX509Handle() : + public SafeX509Handle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -48,7 +48,7 @@ public override bool IsInvalid internal sealed class SafeX509CrlHandle : SafeHandle { - private SafeX509CrlHandle() : + public SafeX509CrlHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -68,7 +68,7 @@ public override bool IsInvalid internal sealed class SafeX509StoreHandle : SafeHandle { - private SafeX509StoreHandle() : + public SafeX509StoreHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs index 0bad8f098921c..e79ff15d52111 100644 --- a/src/libraries/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs +++ b/src/libraries/Common/src/Microsoft/Win32/SafeHandles/X509ExtensionSafeHandles.Unix.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeX509ExtensionHandle : SafeHandle { - private SafeX509ExtensionHandle() : + public SafeX509ExtensionHandle() : base(IntPtr.Zero, ownsHandle: true) { } @@ -29,7 +29,7 @@ public override bool IsInvalid internal sealed class SafeEkuExtensionHandle : SafeHandle { - private SafeEkuExtensionHandle() : + public SafeEkuExtensionHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/src/System/HexConverter.cs b/src/libraries/Common/src/System/HexConverter.cs index 7f244771ca31a..7932b0917dae3 100644 --- a/src/libraries/Common/src/System/HexConverter.cs +++ b/src/libraries/Common/src/System/HexConverter.cs @@ -4,6 +4,12 @@ #nullable disable using System.Diagnostics; using System.Runtime.CompilerServices; +#if SYSTEM_PRIVATE_CORELIB +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using Internal.Runtime.CompilerServices; +#endif namespace System { @@ -84,11 +90,75 @@ public static void ToCharsBuffer(byte value, Span buffer, int startingInde buffer[startingIndex] = (char)(packedResult >> 8); } +#if SYSTEM_PRIVATE_CORELIB + private static void EncodeToUtf16_Ssse3(ReadOnlySpan bytes, Span chars, Casing casing) + { + Debug.Assert(bytes.Length >= 4); + nint pos = 0; + + Vector128 shuffleMask = Vector128.Create( + 0xFF, 0xFF, 0, 0xFF, 0xFF, 0xFF, 1, 0xFF, + 0xFF, 0xFF, 2, 0xFF, 0xFF, 0xFF, 3, 0xFF); + + Vector128 asciiTable = (casing == Casing.Upper) ? + Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'A', (byte)'B', + (byte)'C', (byte)'D', (byte)'E', (byte)'F') : + Vector128.Create((byte)'0', (byte)'1', (byte)'2', (byte)'3', + (byte)'4', (byte)'5', (byte)'6', (byte)'7', + (byte)'8', (byte)'9', (byte)'a', (byte)'b', + (byte)'c', (byte)'d', (byte)'e', (byte)'f'); + + do + { + // Read 32bits from "bytes" span at "pos" offset + uint block = Unsafe.ReadUnaligned( + ref Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos)); + + // Calculate nibbles + Vector128 lowNibbles = Ssse3.Shuffle( + Vector128.CreateScalarUnsafe(block).AsByte(), shuffleMask); + Vector128 highNibbles = Sse2.ShiftRightLogical( + Sse2.ShiftRightLogical128BitLane(lowNibbles, 2).AsInt32(), 4).AsByte(); + + // Lookup the hex values at the positions of the indices + Vector128 indices = Sse2.And( + Sse2.Or(lowNibbles, highNibbles), Vector128.Create((byte)0xF)); + Vector128 hex = Ssse3.Shuffle(asciiTable, indices); + + // The high bytes (0x00) of the chars have also been converted + // to ascii hex '0', so clear them out. + hex = Sse2.And(hex, Vector128.Create((ushort)0xFF).AsByte()); + + // Save to "chars" at pos*2 offset + Unsafe.WriteUnaligned( + ref Unsafe.As( + ref Unsafe.Add(ref MemoryMarshal.GetReference(chars), pos * 2)), hex); + + pos += 4; + } while (pos < bytes.Length - 3); + + // Process trailing elements (bytes.Length % 4) + for (; pos < bytes.Length; pos++) + { + ToCharsBuffer(Unsafe.Add(ref MemoryMarshal.GetReference(bytes), pos), chars, (int)pos * 2, casing); + } + } +#endif + public static void EncodeToUtf16(ReadOnlySpan bytes, Span chars, Casing casing = Casing.Upper) { Debug.Assert(chars.Length >= bytes.Length * 2); - for (int pos = 0; pos < bytes.Length; ++pos) +#if SYSTEM_PRIVATE_CORELIB + if (Ssse3.IsSupported && bytes.Length >= 4) + { + EncodeToUtf16_Ssse3(bytes, chars, casing); + return; + } +#endif + for (int pos = 0; pos < bytes.Length; pos++) { ToCharsBuffer(bytes[pos], chars, pos * 2, casing); } diff --git a/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md b/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md index eb86cd931280e..63c31623360ae 100644 --- a/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md +++ b/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md @@ -1,16 +1,16 @@ -The code in this directory is shared between dotnet/runtime and dotnet/AspNetCore. This contains HTTP/2 and HTTP/3 protocol infrastructure such as an HPACK implementation. Any changes to this dir need to be checked into both repositories. +The code in this directory is shared between dotnet/runtime and dotnet/aspnetcore. This contains HTTP/2 and HTTP/3 protocol infrastructure such as an HPACK implementation. Any changes to this dir need to be checked into both repositories. dotnet/runtime code paths: - runtime\src\libraries\Common\src\System\Net\Http\aspnetcore - runtime\src\libraries\Common\tests\Tests\System\Net\aspnetcore -dotnet/AspNetCore code paths: -- AspNetCore\src\Shared\runtime -- AspNetCore\src\Shared\test\Shared.Tests\runtime +dotnet/aspnetcore code paths: +- aspnetcore\src\Shared\runtime +- aspnetcore\src\Shared\test\Shared.Tests\runtime ## Copying code -- To copy code from dotnet/runtime to dotnet/AspNetCore, set ASPNETCORE_REPO to the AspNetCore repo root and then run CopyToAspNetCore.cmd. -- To copy code from dotnet/AspNetCore to dotnet/runtime, set RUNTIME_REPO to the runtime repo root and then run CopyToRuntime.cmd. +- To copy code from dotnet/runtime to dotnet/aspnetcore, set ASPNETCORE_REPO to the aspnetcore repo root and then run CopyToAspNetCore.cmd. +- To copy code from dotnet/aspnetcore to dotnet/runtime, set RUNTIME_REPO to the runtime repo root and then run CopyToRuntime.cmd. ## Building dotnet/runtime code: - https://github.com/dotnet/runtime/tree/master/docs/workflow @@ -23,14 +23,14 @@ dotnet/AspNetCore code paths: - `PS D:\github\runtime\src\libraries\Common\tests> dotnet build /t:test` - `PS D:\github\runtime\src\libraries\System.Net.Http\tests\UnitTests> dotnet build /t:test` -## Building dotnet/AspNetCore code: -- https://github.com/dotnet/AspNetCore/blob/master/docs/BuildFromSource.md -- Run restore in the root once: `PS D:\github\AspNetCore> .\restore.cmd` -- Activate to use the repo local runtime: `PS D:\github\AspNetCore> . .\activate.ps1` +## Building dotnet/aspnetcore code: +- https://github.com/dotnet/aspnetcore/blob/main/docs/BuildFromSource.md +- Run restore in the root once: `PS D:\github\aspnetcore> .\restore.cmd` +- Activate to use the repo local runtime: `PS D:\github\aspnetcore> . .\activate.ps1` - Build the individual projects: -- `(AspNetCore) PS D:\github\AspNetCore\src\Shared\test\Shared.Tests> dotnet build` -- `(AspNetCore) PS D:\github\AspNetCore\src\servers\Kestrel\core\src> dotnet build` +- `(aspnetcore) PS D:\github\aspnetcore\src\Shared\test\Shared.Tests> dotnet build` +- `(aspnetcore) PS D:\github\aspnetcore\src\servers\Kestrel\core\src> dotnet build` -### Running dotnet/AspNetCore tests: -- `(AspNetCore) PS D:\github\AspNetCore\src\Shared\test\Shared.Tests> dotnet test` -- `(AspNetCore) PS D:\github\AspNetCore\src\servers\Kestrel\core\test> dotnet test` +### Running dotnet/aspnetcore tests: +- `(aspnetcore) PS D:\github\aspnetcore\src\Shared\test\Shared.Tests> dotnet test` +- `(aspnetcore) PS D:\github\aspnetcore\src\servers\Kestrel\core\test> dotnet test` diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs index 63c13bdac297c..78ee42c9301f4 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs @@ -94,9 +94,4 @@ public override Task CreateConnectionAsync(Socket soc throw new NotImplementedException("HTTP/3 does not operate over a Socket."); } } - - public static class HttpVersion30 - { - public static readonly Version Value = new Version(3, 0); - } } diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs index 3286aff03a282..da0309ef78cbb 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs @@ -201,7 +201,7 @@ private HttpRequestData ParseHeaders(ReadOnlySpan buffer) break; } } - request.Version = HttpVersion30.Value; + request.Version = HttpVersion.Version30; return request; } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs new file mode 100644 index 0000000000000..a491691e0494a --- /dev/null +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.RemoteServer.cs @@ -0,0 +1,899 @@ +// 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.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.Functional.Tests +{ + using Configuration = System.Net.Test.Common.Configuration; + +#if WINHTTPHANDLER_TEST + using HttpClientHandler = System.Net.Http.WinHttpClientHandler; +#endif + + public sealed class HttpClientHandler_RemoteServerTest : HttpClientHandlerTestBase + { + private const string ExpectedContent = "Test content"; + private const string Username = "testuser"; + private const string Password = "password"; + + private readonly NetworkCredential _credential = new NetworkCredential(Username, Password); + + public static readonly object[][] Http2Servers = Configuration.Http.Http2Servers; + public static readonly object[][] Http2NoPushServers = Configuration.Http.Http2NoPushServers; + + // Standard HTTP methods defined in RFC7231: http://tools.ietf.org/html/rfc7231#section-4.3 + // "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE" + public static readonly IEnumerable HttpMethods = + GetMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CUSTOM1"); + public static readonly IEnumerable HttpMethodsThatAllowContent = + GetMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "CUSTOM1"); + public static readonly IEnumerable HttpMethodsThatDontAllowContent = + GetMethods("HEAD", "TRACE"); + + private static bool IsWindows10Version1607OrGreater => PlatformDetection.IsWindows10Version1607OrGreater; + + private static IEnumerable GetMethods(params string[] methods) + { + foreach (string method in methods) + { + foreach (Uri serverUri in Configuration.Http.EchoServerList) + { + yield return new object[] { method, serverUri }; + } + } + } + + public HttpClientHandler_RemoteServerTest(ITestOutputHelper output) : base(output) + { + } + + [OuterLoop("Uses external servers")] + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task UseDefaultCredentials_SetToFalseAndServerNeedsAuth_StatusCodeUnauthorized(bool useProxy) + { + HttpClientHandler handler = CreateHttpClientHandler(); + handler.UseProxy = useProxy; + handler.UseDefaultCredentials = false; + using (HttpClient client = CreateHttpClient(handler)) + { + Uri uri = Configuration.Http.RemoteHttp11Server.NegotiateAuthUriForDefaultCreds; + _output.WriteLine("Uri: {0}", uri); + using (HttpResponseMessage response = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + } + } + + [OuterLoop("Uses external servers")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task SendAsync_SimpleGet_Success(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) + { + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + null); + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task SendAsync_MultipleRequestsReusingSameClient_Success(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + for (int i = 0; i < 3; i++) + { + using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task GetAsync_ResponseContentAfterClientAndHandlerDispose_Success(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) + { + client.Dispose(); + Assert.NotNull(response); + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody(responseContent, response.Content.Headers.ContentMD5, false, null); + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task GetAsync_ServerNeedsBasicAuthAndSetDefaultCredentials_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer) + { + HttpClientHandler handler = CreateHttpClientHandler(); + handler.Credentials = CredentialCache.DefaultCredentials; + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) + { + Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password); + using (HttpResponseMessage response = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task GetAsync_ServerNeedsAuthAndSetCredential_StatusCodeOK(Configuration.Http.RemoteServer remoteServer) + { + HttpClientHandler handler = CreateHttpClientHandler(); + handler.Credentials = _credential; + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) + { + Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password); + using (HttpResponseMessage response = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password); + using (HttpResponseMessage response = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + } + } + + [OuterLoop("Uses external server")] + [Theory] + [MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))] + public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndEmptyValueSent(Configuration.Http.RemoteServer remoteServer, Uri uri) + { + if (IsWinHttpHandler && !PlatformDetection.IsWindows10Version1709OrGreater) + { + return; + } + + string name = "X-Cust-Header-NoValue"; + string value = ""; + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + _output.WriteLine($"name={name}, value={value}"); + client.DefaultRequestHeaders.Add(name, value); + using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + string responseText = await httpResponse.Content.ReadAsStringAsync(); + _output.WriteLine(responseText); + Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value)); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersHeaderValuesAndUris))] + public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndValueSent(Configuration.Http.RemoteServer remoteServer, string name, string value, Uri uri) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + _output.WriteLine($"name={name}, value={value}"); + client.DefaultRequestHeaders.Add(name, value); + using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + string responseText = await httpResponse.Content.ReadAsStringAsync(); + _output.WriteLine(responseText); + Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value)); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))] + public async Task GetAsync_LargeRequestHeader_HeadersAndValuesSent(Configuration.Http.RemoteServer remoteServer, Uri uri) + { + // Unfortunately, our remote servers seem to have pretty strict limits (around 16K?) + // on the total size of the request header. + // TODO: Figure out how to reconfigure remote endpoints to allow larger request headers, + // and then increase the limits in this test. + + string headerValue = new string('a', 2048); + const int headerCount = 6; + + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + for (int i = 0; i < headerCount; i++) + { + client.DefaultRequestHeaders.Add($"Header-{i}", headerValue); + } + + using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) + { + Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); + string responseText = await httpResponse.Content.ReadAsStringAsync(); + + for (int i = 0; i < headerCount; i++) + { + Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, $"Header-{i}", headerValue)); + } + } + } + } + + public static IEnumerable RemoteServersHeaderValuesAndUris() + { + foreach ((Configuration.Http.RemoteServer remoteServer, Uri uri) in RemoteServersAndHeaderEchoUris()) + { + yield return new object[] { remoteServer, "X-CustomHeader", "x-value", uri }; + yield return new object[] { remoteServer, "MyHeader", "1, 2, 3", uri }; + + // Construct a header value with every valid character (except space) + string allchars = ""; + for (int i = 0x21; i <= 0x7E; i++) + { + allchars = allchars + (char)i; + } + + // Put a space in the middle so it's not interpreted as insignificant leading/trailing whitespace + allchars = allchars + " " + allchars; + + yield return new object[] { remoteServer, "All-Valid-Chars-Header", allchars, uri }; + } + } + + public static IEnumerable<(Configuration.Http.RemoteServer remoteServer, Uri uri)> RemoteServersAndHeaderEchoUris() + { + foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) + { + yield return (remoteServer, remoteServer.EchoUri); + yield return (remoteServer, remoteServer.RedirectUriForDestinationUri( + statusCode: 302, + destinationUri: remoteServer.EchoUri, + hops: 1)); + } + } + + public static IEnumerable RemoteServersAndHeaderEchoUrisMemberData() => RemoteServersAndHeaderEchoUris().Select(x => new object[] { x.remoteServer, x.uri }); + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task GetAsync_ResponseHeadersRead_ReadFromEachIterativelyDoesntDeadlock(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + const int NumGets = 5; + Task[] responseTasks = (from _ in Enumerable.Range(0, NumGets) + select client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead)).ToArray(); + for (int i = responseTasks.Length - 1; i >= 0; i--) // read backwards to increase likelihood that we wait on a different task than has data available + { + using (HttpResponseMessage response = await responseTasks[i]) + { + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + null); + } + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task SendAsync_HttpRequestMsgResponseHeadersRead_StatusCodeOK(Configuration.Http.RemoteServer remoteServer) + { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync && remoteServer.HttpVersion.Major >= 2) + { + return; + } + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, remoteServer.EchoUri) { Version = remoteServer.HttpVersion }; + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request, HttpCompletionOption.ResponseHeadersRead)) + { + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + null); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_CallMethodTwice_StringContent(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + string data = "Test String"; + var content = new StringContent(data, Encoding.UTF8); + content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); + HttpResponseMessage response; + using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + // Repeat call. + content = new StringContent(data, Encoding.UTF8); + content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); + using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_CallMethod_UnicodeStringContent(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + string data = "\ub4f1\uffc7\u4e82\u67ab4\uc6d4\ud1a0\uc694\uc77c\uffda3\u3155\uc218\uffdb"; + var content = new StringContent(data, Encoding.UTF8); + content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); + + using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(VerifyUploadServersStreamsAndExpectedData))] + public async Task PostAsync_CallMethod_StreamContent(Configuration.Http.RemoteServer remoteServer, HttpContent content, byte[] expectedData) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(expectedData); + + using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + } + } + + private sealed class StreamContentWithSyncAsyncCopy : StreamContent + { + private readonly Stream _stream; + private readonly bool _syncCopy; + + public StreamContentWithSyncAsyncCopy(Stream stream, bool syncCopy) : base(stream) + { + _stream = stream; + _syncCopy = syncCopy; + } + + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + if (_syncCopy) + { + try + { + _stream.CopyTo(stream, 128); // arbitrary size likely to require multiple read/writes + return Task.CompletedTask; + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + + return base.SerializeToStreamAsync(stream, context); + } + } + + public static IEnumerable VerifyUploadServersStreamsAndExpectedData + { + get + { + foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) // target server + foreach (bool syncCopy in BoolValues) // force the content copy to happen via Read/Write or ReadAsync/WriteAsync + { + byte[] data = new byte[1234]; + new Random(42).NextBytes(data); + + // A MemoryStream + { + var memStream = new MemoryStream(data, writable: false); + yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(memStream, syncCopy: syncCopy), data }; + } + + // A multipart content that provides its own stream from CreateContentReadStreamAsync + { + var mc = new MultipartContent(); + mc.Add(new ByteArrayContent(data)); + var memStream = new MemoryStream(); + mc.CopyToAsync(memStream).GetAwaiter().GetResult(); + yield return new object[] { remoteServer, mc, memStream.ToArray() }; + } + + // A stream that provides the data synchronously and has a known length + { + var wrappedMemStream = new MemoryStream(data, writable: false); + var syncKnownLengthStream = new DelegateStream( + canReadFunc: () => wrappedMemStream.CanRead, + canSeekFunc: () => wrappedMemStream.CanSeek, + lengthFunc: () => wrappedMemStream.Length, + positionGetFunc: () => wrappedMemStream.Position, + positionSetFunc: p => wrappedMemStream.Position = p, + readFunc: (buffer, offset, count) => wrappedMemStream.Read(buffer, offset, count), + readAsyncFunc: (buffer, offset, count, token) => wrappedMemStream.ReadAsync(buffer, offset, count, token)); + yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncKnownLengthStream, syncCopy: syncCopy), data }; + } + + // A stream that provides the data synchronously and has an unknown length + { + int syncUnknownLengthStreamOffset = 0; + + Func readFunc = (buffer, offset, count) => + { + int bytesRemaining = data.Length - syncUnknownLengthStreamOffset; + int bytesToCopy = Math.Min(bytesRemaining, count); + Array.Copy(data, syncUnknownLengthStreamOffset, buffer, offset, bytesToCopy); + syncUnknownLengthStreamOffset += bytesToCopy; + return bytesToCopy; + }; + + var syncUnknownLengthStream = new DelegateStream( + canReadFunc: () => true, + canSeekFunc: () => false, + readFunc: readFunc, + readAsyncFunc: (buffer, offset, count, token) => Task.FromResult(readFunc(buffer, offset, count))); + yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncUnknownLengthStream, syncCopy: syncCopy), data }; + } + + // A stream that provides the data asynchronously + { + int asyncStreamOffset = 0, maxDataPerRead = 100; + + Func readFunc = (buffer, offset, count) => + { + int bytesRemaining = data.Length - asyncStreamOffset; + int bytesToCopy = Math.Min(bytesRemaining, Math.Min(maxDataPerRead, count)); + Array.Copy(data, asyncStreamOffset, buffer, offset, bytesToCopy); + asyncStreamOffset += bytesToCopy; + return bytesToCopy; + }; + + var asyncStream = new DelegateStream( + canReadFunc: () => true, + canSeekFunc: () => false, + readFunc: readFunc, + readAsyncFunc: async (buffer, offset, count, token) => + { + await Task.Delay(1).ConfigureAwait(false); + return readFunc(buffer, offset, count); + }); + yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(asyncStream, syncCopy: syncCopy), data }; + } + + // Providing data from a FormUrlEncodedContent's stream + { + var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair("key", "val") }); + yield return new object[] { remoteServer, formContent, Encoding.GetEncoding("iso-8859-1").GetBytes("key=val") }; + } + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_CallMethod_NullContent(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + string.Empty); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_CallMethod_EmptyContent(Configuration.Http.RemoteServer remoteServer) + { + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + var content = new StringContent(string.Empty); + using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + string.Empty); + } + } + } + + public static IEnumerable ExpectContinueVersion() + { + return + from expect in new bool?[] {true, false, null} + from version in new Version[] {new Version(1, 0), new Version(1, 1), new Version(2, 0)} + select new object[] {expect, version}; + } + + [OuterLoop("Uses external server")] + [Theory] + [MemberData(nameof(ExpectContinueVersion))] + public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, Version version) + { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync && version.Major >= 2) + { + return; + } + + using (HttpClient client = CreateHttpClient()) + { + var req = new HttpRequestMessage(HttpMethod.Post, version.Major == 2 ? Configuration.Http.Http2RemoteEchoServer : Configuration.Http.RemoteEchoServer) + { + Content = new StringContent("Test String", Encoding.UTF8), + Version = version + }; + req.Headers.ExpectContinue = expectContinue; + + using (HttpResponseMessage response = await client.SendAsync(TestAsync, req)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + if (!IsWinHttpHandler) + { + const string ExpectedReqHeader = "\"Expect\": \"100-continue\""; + if (expectContinue == true && (version >= new Version(1, 1))) + { + Assert.Contains(ExpectedReqHeader, await response.Content.ReadAsStringAsync()); + } + else + { + Assert.DoesNotContain(ExpectedReqHeader, await response.Content.ReadAsStringAsync()); + } + } + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_Redirect_ResultingGetFormattedCorrectly(Configuration.Http.RemoteServer remoteServer) + { + const string ContentString = "This is the content string."; + var content = new StringContent(ContentString); + Uri redirectUri = remoteServer.RedirectUriForDestinationUri( + 302, + remoteServer.EchoUri, + 1); + + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + using (HttpResponseMessage response = await client.PostAsync(redirectUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + string responseContent = await response.Content.ReadAsStringAsync(); + Assert.DoesNotContain(ContentString, responseContent); + Assert.DoesNotContain("Content-Length", responseContent); + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_RedirectWith307_LargePayload(Configuration.Http.RemoteServer remoteServer) + { + if (remoteServer.HttpVersion == new Version(2, 0)) + { + // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux + // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix. + // Disable until we can investigate further. + return; + } + + await PostAsync_Redirect_LargePayload_Helper(remoteServer, 307, true); + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_RedirectWith302_LargePayload(Configuration.Http.RemoteServer remoteServer) + { + await PostAsync_Redirect_LargePayload_Helper(remoteServer, 302, false); + } + + private async Task PostAsync_Redirect_LargePayload_Helper(Configuration.Http.RemoteServer remoteServer, int statusCode, bool expectRedirectToPost) + { + using (var fs = new FileStream( + Path.Combine(Path.GetTempPath(), Path.GetTempFileName()), + FileMode.Create, + FileAccess.ReadWrite, + FileShare.None, + 0x1000, + FileOptions.DeleteOnClose)) + { + string contentString = string.Join("", Enumerable.Repeat("Content", 100000)); + byte[] contentBytes = Encoding.UTF32.GetBytes(contentString); + fs.Write(contentBytes, 0, contentBytes.Length); + fs.Flush(flushToDisk: true); + fs.Position = 0; + + Uri redirectUri = remoteServer.RedirectUriForDestinationUri( + statusCode: statusCode, + destinationUri: remoteServer.VerifyUploadUri, + hops: 1); + var content = new StreamContent(fs); + + // Compute MD5 of request body data. This will be verified by the server when it receives the request. + content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(contentBytes); + + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + using (HttpResponseMessage response = await client.PostAsync(redirectUri, content)) + { + try + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + catch + { + _output.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}"); + throw; + } + + if (expectRedirectToPost) + { + IEnumerable headerValue = response.Headers.GetValues("X-HttpRequest-Method"); + Assert.Equal("POST", headerValue.First()); + } + } + } + } + +#if !NETFRAMEWORK + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(RemoteServersMemberData))] + public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer) + { + const string ContentString = "This is the content string."; + using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + { + var content = new StringContent(ContentString); + for (int i = 0; i < 2; i++) + { + using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains(ContentString, await response.Content.ReadAsStringAsync()); + } + } + } + } +#endif + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(HttpMethods))] + public async Task SendAsync_SendRequestUsingMethodToEchoServerWithNoContent_MethodCorrectlySent( + string method, + Uri serverUri) + { + using (HttpClient client = CreateHttpClient()) + { + var request = new HttpRequestMessage( + new HttpMethod(method), + serverUri) { Version = UseVersion }; + + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + TestHelper.VerifyRequestMethod(response, method); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(HttpMethodsThatAllowContent))] + public async Task SendAsync_SendRequestUsingMethodToEchoServerWithContent_Success( + string method, + Uri serverUri) + { + using (HttpClient client = CreateHttpClient()) + { + var request = new HttpRequestMessage( + new HttpMethod(method), + serverUri) { Version = UseVersion }; + request.Content = new StringContent(ExpectedContent); + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + TestHelper.VerifyRequestMethod(response, method); + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + + Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + ExpectedContent); + } + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(HttpMethodsThatDontAllowContent))] + public async Task SendAsync_SendRequestUsingNoBodyMethodToEchoServerWithContent_NoBodySent( + string method, + Uri serverUri) + { + using (HttpClient client = CreateHttpClient()) + { + var request = new HttpRequestMessage( + new HttpMethod(method), + serverUri) + { + Content = new StringContent(ExpectedContent), + Version = UseVersion + }; + + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) + { + if (method == "TRACE") + { + // .NET Framework also allows the HttpWebRequest and HttpClient APIs to send a request using 'TRACE' + // verb and a request body. The usual response from a server is "400 Bad Request". + // See here for more info: https://github.com/dotnet/runtime/issues/17475 + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + else + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + TestHelper.VerifyRequestMethod(response, method); + string responseContent = await response.Content.ReadAsStringAsync(); + Assert.DoesNotContain(ExpectedContent, responseContent); + } + } + } + } + + public static IEnumerable SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success_MemberData() + { + foreach (var server in Configuration.Http.RemoteServers) + { + yield return new object[] { server, "12345678910", 0 }; + yield return new object[] { server, "12345678910", 5 }; + } + } + + [OuterLoop("Uses external server")] + [Theory, MemberData(nameof(SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success_MemberData))] + public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success(Configuration.Http.RemoteServer remoteServer, string stringContent, int startingPosition) + { + using (var handler = new HttpMessageInvoker(CreateHttpClientHandler())) + { + byte[] byteContent = Encoding.ASCII.GetBytes(stringContent); + var content = new MemoryStream(); + content.Write(byteContent, 0, byteContent.Length); + content.Position = startingPosition; + var request = new HttpRequestMessage(HttpMethod.Post, remoteServer.EchoUri) { Content = new StreamContent(content), Version = UseVersion }; + + for (int iter = 0; iter < 2; iter++) + { + using (HttpResponseMessage response = await handler.SendAsync(TestAsync, request, CancellationToken.None)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + string responseContent = await response.Content.ReadAsStringAsync(); + + Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent); + string bodyContent = System.Text.Json.JsonDocument.Parse(responseContent).RootElement.GetProperty("BodyContent").GetString(); + Assert.Contains(stringContent.Substring(startingPosition), bodyContent); + if (startingPosition != 0) + { + Assert.DoesNotContain(stringContent.Substring(0, startingPosition), bodyContent); + } + } + } + } + } + + [OuterLoop("Uses external server")] + [Theory] + [MemberData(nameof(Http2Servers))] + public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(Uri server) + { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync) + { + return; + } + + // We don't currently have a good way to test whether HTTP/2 is supported without + // using the same mechanism we're trying to test, so for now we allow both 2.0 and 1.1 responses. + var request = new HttpRequestMessage(HttpMethod.Get, server); + request.Version = new Version(2, 0); + + using (HttpClient client = CreateHttpClient()) + { + // It is generally expected that the test hosts will be trusted, so we don't register a validation + // callback in the usual case. + + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True( + response.Version == new Version(2, 0) || + response.Version == new Version(1, 1), + "Response version " + response.Version); + } + } + } + + [OuterLoop("Uses external server")] + [ConditionalTheory(nameof(IsWindows10Version1607OrGreater)), MemberData(nameof(Http2NoPushServers))] + public async Task SendAsync_RequestVersion20_ResponseVersion20(Uri server) + { + // Sync API supported only up to HTTP/1.1 + if (!TestAsync) + { + return; + } + + _output.WriteLine(server.AbsoluteUri.ToString()); + var request = new HttpRequestMessage(HttpMethod.Get, server); + request.Version = new Version(2, 0); + + using (HttpClient client = CreateHttpClient()) + { + using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(new Version(2, 0), response.Version); + } + } + } + } +} diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 226a5aec3e24d..2cb88d361acd5 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -1,28 +1,23 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http.Headers; using System.Net.Sockets; using System.Net.Test.Common; -using System.Runtime.InteropServices; using System.Security.Authentication; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; -using Microsoft.DotNet.RemoteExecutor; using Xunit; using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { - using Configuration = System.Net.Test.Common.Configuration; - #if WINHTTPHANDLER_TEST using HttpClientHandler = System.Net.Http.WinHttpClientHandler; #endif @@ -31,38 +26,6 @@ namespace System.Net.Http.Functional.Tests // to separately Dispose (or have a 'using' statement) for the handler. public abstract class HttpClientHandlerTest : HttpClientHandlerTestBase { - private const string ExpectedContent = "Test content"; - private const string Username = "testuser"; - private const string Password = "password"; - private const string HttpDefaultPort = "80"; - - private readonly NetworkCredential _credential = new NetworkCredential(Username, Password); - - public static readonly object[][] Http2Servers = Configuration.Http.Http2Servers; - public static readonly object[][] Http2NoPushServers = Configuration.Http.Http2NoPushServers; - - // Standard HTTP methods defined in RFC7231: http://tools.ietf.org/html/rfc7231#section-4.3 - // "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE" - public static readonly IEnumerable HttpMethods = - GetMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CUSTOM1"); - public static readonly IEnumerable HttpMethodsThatAllowContent = - GetMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "CUSTOM1"); - public static readonly IEnumerable HttpMethodsThatDontAllowContent = - GetMethods("HEAD", "TRACE"); - - private static bool IsWindows10Version1607OrGreater => PlatformDetection.IsWindows10Version1607OrGreater; - - private static IEnumerable GetMethods(params string[] methods) - { - foreach (string method in methods) - { - foreach (Uri serverUri in Configuration.Http.EchoServerList) - { - yield return new object[] { method, serverUri }; - } - } - } - public HttpClientHandlerTest(ITestOutputHelper output) : base(output) { } @@ -157,32 +120,6 @@ public void MaxRequestContentBufferSize_SetInvalidValue_ThrowsArgumentOutOfRange } #endif - [OuterLoop("Uses external servers")] - [Theory] - [InlineData(false)] - [InlineData(true)] - [OuterLoop("Uses external servers")] - public async Task UseDefaultCredentials_SetToFalseAndServerNeedsAuth_StatusCodeUnauthorized(bool useProxy) - { - if (UseVersion == HttpVersion30) - { - return; - } - - HttpClientHandler handler = CreateHttpClientHandler(); - handler.UseProxy = useProxy; - handler.UseDefaultCredentials = false; - using (HttpClient client = CreateHttpClient(handler)) - { - Uri uri = Configuration.Http.RemoteHttp11Server.NegotiateAuthUriForDefaultCreds; - _output.WriteLine("Uri: {0}", uri); - using (HttpResponseMessage response = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - } - } - [Fact] public void Properties_Get_CountIsZero() { @@ -210,23 +147,6 @@ public void Properties_AddItemToDictionary_ItemPresent() } } - [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task SendAsync_SimpleGet_Success(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) - { - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - null); - } - } - [Fact] public async Task GetAsync_IPv6LinkLocalAddressUri_Success() { @@ -301,37 +221,6 @@ public static IEnumerable GetAsync_IPBasedUri_Success_MemberData() } } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task SendAsync_MultipleRequestsReusingSameClient_Success(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - for (int i = 0; i < 3; i++) - { - using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task GetAsync_ResponseContentAfterClientAndHandlerDispose_Success(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri)) - { - client.Dispose(); - Assert.NotNull(response); - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody(responseContent, response.Content.Headers.ContentMD5, false, null); - } - } - [Theory] [InlineData("[::1234]")] [InlineData("[::1234]:8080")] @@ -398,17 +287,16 @@ await LoopbackServer.CreateClientAndServerAsync(async proxyUri => public static IEnumerable DestinationHost_MemberData() { - yield return new object[] { Configuration.Http.Host }; + yield return new object[] { "nosuchhost.invalid" }; yield return new object[] { "1.2.3.4" }; yield return new object[] { "[::1234]" }; } [Theory] - [OuterLoop("Uses external server")] [MemberData(nameof(DestinationHost_MemberData))] public async Task ProxiedRequest_DefaultPort_PortStrippedOffInUri(string host) { - string addressUri = $"http://{host}:{HttpDefaultPort}/"; + string addressUri = $"http://{host}:80/"; string expectedAddressUri = $"http://{host}/"; bool connectionAccepted = false; @@ -435,13 +323,13 @@ await LoopbackServer.CreateClientAndServerAsync(async proxyUri => Assert.True(connectionAccepted); } - [Fact] - [OuterLoop("Uses external server")] - public async Task ProxyTunnelRequest_PortSpecified_NotStrippedOffInUri() + [Theory] + [MemberData(nameof(DestinationHost_MemberData))] + public async Task ProxyTunnelRequest_PortSpecified_NotStrippedOffInUri(string host) { // Https proxy request will use CONNECT tunnel, even the default 443 port is specified, it will not be stripped off. - string requestTarget = $"{Configuration.Http.SecureHost}:443"; - string addressUri = $"https://{requestTarget}/"; + string requestTarget = $"{host}:443"; + string addressUri = $"https://{host}/"; bool connectionAccepted = false; if (UseVersion == HttpVersion30) @@ -471,7 +359,6 @@ await LoopbackServer.CreateClientAndServerAsync(async proxyUri => [Theory] [InlineData(true)] [InlineData(false)] - [OuterLoop("Uses external server")] public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeader) { if (IsWinHttpHandler) @@ -484,7 +371,8 @@ public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeade return; } - string addressUri = $"https://{Configuration.Http.SecureHost}/"; + string host = "nosuchhost.invalid"; + string addressUri = $"https://{host}/"; bool connectionAccepted = false; await LoopbackServer.CreateClientAndServerAsync(async proxyUri => @@ -510,7 +398,7 @@ await LoopbackServer.CreateClientAndServerAsync(async proxyUri => { connectionAccepted = true; List headers = await connection.ReadRequestHeaderAndSendResponseAsync(); - Assert.Contains($"CONNECT {Configuration.Http.SecureHost}:443 HTTP/1.1", headers); + Assert.Contains($"CONNECT {host}:443 HTTP/1.1", headers); if (addUserAgentHeader) { Assert.Contains("User-Agent: Mozilla/5.0", headers); @@ -565,67 +453,6 @@ await LoopbackServer.CreateClientAndServerAsync(async url => Assert.True(connectionAccepted); } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task GetAsync_ServerNeedsBasicAuthAndSetDefaultCredentials_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer) - { - if (UseVersion == HttpVersion30) - { - return; - } - - HttpClientHandler handler = CreateHttpClientHandler(); - handler.Credentials = CredentialCache.DefaultCredentials; - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) - { - Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password); - using (HttpResponseMessage response = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task GetAsync_ServerNeedsAuthAndSetCredential_StatusCodeOK(Configuration.Http.RemoteServer remoteServer) - { - if (UseVersion == HttpVersion30) - { - return; - } - - HttpClientHandler handler = CreateHttpClientHandler(); - handler.Credentials = _credential; - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler)) - { - Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password); - using (HttpResponseMessage response = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - } - } - - [OuterLoop("Uses external server")] - [Fact] - public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized() - { - if (UseVersion == HttpVersion30) - { - return; - } - - using (HttpClient client = CreateHttpClient(UseVersion.ToString())) - { - Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: Username, password: Password); - using (HttpResponseMessage response = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - } - } - [Theory] [InlineData("WWW-Authenticate", "CustomAuth")] [InlineData("", "")] // RFC7235 requires servers to send this header with 401 but some servers don't. @@ -655,132 +482,6 @@ await LoopbackServerFactory.CreateServerAsync(async (server, url) => }); } - [OuterLoop("Uses external server")] - [Theory] - [MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))] - public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndEmptyValueSent(Configuration.Http.RemoteServer remoteServer, Uri uri) - { - if (IsWinHttpHandler && !PlatformDetection.IsWindows10Version1709OrGreater) - { - return; - } - - if (UseVersion == HttpVersion30) - { - return; - } - - string name = "X-Cust-Header-NoValue"; - string value = ""; - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - _output.WriteLine($"name={name}, value={value}"); - client.DefaultRequestHeaders.Add(name, value); - using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); - string responseText = await httpResponse.Content.ReadAsStringAsync(); - _output.WriteLine(responseText); - Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value)); - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersHeaderValuesAndUris))] - public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndValueSent(Configuration.Http.RemoteServer remoteServer, string name, string value, Uri uri) - { - if (UseVersion == HttpVersion30) - { - return; - } - - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - _output.WriteLine($"name={name}, value={value}"); - client.DefaultRequestHeaders.Add(name, value); - using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); - string responseText = await httpResponse.Content.ReadAsStringAsync(); - _output.WriteLine(responseText); - Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value)); - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))] - public async Task GetAsync_LargeRequestHeader_HeadersAndValuesSent(Configuration.Http.RemoteServer remoteServer, Uri uri) - { - if (UseVersion == HttpVersion30) - { - return; - } - - // Unfortunately, our remote servers seem to have pretty strict limits (around 16K?) - // on the total size of the request header. - // TODO: Figure out how to reconfigure remote endpoints to allow larger request headers, - // and then increase the limits in this test. - - string headerValue = new string('a', 2048); - const int headerCount = 6; - - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - for (int i = 0; i < headerCount; i++) - { - client.DefaultRequestHeaders.Add($"Header-{i}", headerValue); - } - - using (HttpResponseMessage httpResponse = await client.GetAsync(uri)) - { - Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); - string responseText = await httpResponse.Content.ReadAsStringAsync(); - - for (int i = 0; i < headerCount; i++) - { - Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, $"Header-{i}", headerValue)); - } - } - } - } - - public static IEnumerable RemoteServersHeaderValuesAndUris() - { - foreach ((Configuration.Http.RemoteServer remoteServer, Uri uri) in RemoteServersAndHeaderEchoUris()) - { - yield return new object[] { remoteServer, "X-CustomHeader", "x-value", uri }; - yield return new object[] { remoteServer, "MyHeader", "1, 2, 3", uri }; - - // Construct a header value with every valid character (except space) - string allchars = ""; - for (int i = 0x21; i <= 0x7E; i++) - { - allchars = allchars + (char)i; - } - - // Put a space in the middle so it's not interpreted as insignificant leading/trailing whitespace - allchars = allchars + " " + allchars; - - yield return new object[] { remoteServer, "All-Valid-Chars-Header", allchars, uri }; - } - } - - public static IEnumerable<(Configuration.Http.RemoteServer remoteServer, Uri uri)> RemoteServersAndHeaderEchoUris() - { - foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) - { - yield return (remoteServer, remoteServer.EchoUri); - yield return (remoteServer, remoteServer.RedirectUriForDestinationUri( - statusCode: 302, - destinationUri: remoteServer.EchoUri, - hops: 1)); - } - } - - public static IEnumerable RemoteServersAndHeaderEchoUrisMemberData() => RemoteServersAndHeaderEchoUris().Select(x => new object[] { x.remoteServer, x.uri }); - [Theory] [InlineData(":")] [InlineData("\x1234: \x5678")] @@ -1299,57 +1000,6 @@ public async Task SendAsync_TransferEncodingSetButNoRequestContent_Throws() } } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task GetAsync_ResponseHeadersRead_ReadFromEachIterativelyDoesntDeadlock(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - const int NumGets = 5; - Task[] responseTasks = (from _ in Enumerable.Range(0, NumGets) - select client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead)).ToArray(); - for (int i = responseTasks.Length - 1; i >= 0; i--) // read backwards to increase likelihood that we wait on a different task than has data available - { - using (HttpResponseMessage response = await responseTasks[i]) - { - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - null); - } - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task SendAsync_HttpRequestMsgResponseHeadersRead_StatusCodeOK(Configuration.Http.RemoteServer remoteServer) - { - // Sync API supported only up to HTTP/1.1 - if (!TestAsync && remoteServer.HttpVersion.Major >= 2) - { - return; - } - - HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, remoteServer.EchoUri) { Version = remoteServer.HttpVersion }; - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request, HttpCompletionOption.ResponseHeadersRead)) - { - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - null); - } - } - } - [OuterLoop("Slow response")] [Fact] public async Task SendAsync_ReadFromSlowStreamingServer_PartialDataReturned() @@ -1759,338 +1409,66 @@ public async Task GetAsync_UnicodeHostName_SuccessStatusCodeInResponse() #region Post Methods Tests - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_CallMethodTwice_StringContent(Configuration.Http.RemoteServer remoteServer) + [Fact] + public async Task GetAsync_ExpectContinueTrue_NoContent_StillSendsHeader() { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) { - string data = "Test String"; - var content = new StringContent(data, Encoding.UTF8); - content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); - HttpResponseMessage response; - using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - // Repeat call. - content = new StringContent(data, Encoding.UTF8); - content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); - using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + return; } - } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_CallMethod_UnicodeStringContent(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + if (UseVersion == HttpVersion30) { - string data = "\ub4f1\uffc7\u4e82\u67ab4\uc6d4\ud1a0\uc694\uc77c\uffda3\u3155\uc218\uffdb"; - var content = new StringContent(data, Encoding.UTF8); - content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data); - - using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } + // TODO: ActiveIssue + return; } - } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(VerifyUploadServersStreamsAndExpectedData))] - public async Task PostAsync_CallMethod_StreamContent(Configuration.Http.RemoteServer remoteServer, HttpContent content, byte[] expectedData) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) + const string ExpectedContent = "Hello, expecting and continuing world."; + var clientCompleted = new TaskCompletionSource(); + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { - content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(expectedData); - - using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content)) + using (HttpClient client = CreateHttpClient()) { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + client.DefaultRequestHeaders.ExpectContinue = true; + Assert.Equal(ExpectedContent, await client.GetStringAsync(uri)); + clientCompleted.SetResult(true); } - } + }, async server => + { + await server.AcceptConnectionAsync(async connection => + { + HttpRequestData requestData = await connection.ReadRequestDataAsync(); + Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect")); + + await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false); + await connection.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", ExpectedContent.Length.ToString()) }, isFinal: false); + await connection.SendResponseBodyAsync(ExpectedContent); + await clientCompleted.Task; // make sure server closing the connection isn't what let the client complete + }); + }); } - private sealed class StreamContentWithSyncAsyncCopy : StreamContent + public static IEnumerable Interim1xxStatusCode() { - private readonly Stream _stream; - private readonly bool _syncCopy; + yield return new object[] { (HttpStatusCode) 100 }; // 100 Continue. + // 101 SwitchingProtocols will be treated as a final status code. + yield return new object[] { (HttpStatusCode) 102 }; // 102 Processing. + yield return new object[] { (HttpStatusCode) 103 }; // 103 EarlyHints. + yield return new object[] { (HttpStatusCode) 150 }; + yield return new object[] { (HttpStatusCode) 180 }; + yield return new object[] { (HttpStatusCode) 199 }; + } - public StreamContentWithSyncAsyncCopy(Stream stream, bool syncCopy) : base(stream) + [Theory] + [MemberData(nameof(Interim1xxStatusCode))] + public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnored(HttpStatusCode responseStatusCode) + { + if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) { - _stream = stream; - _syncCopy = syncCopy; + return; } - protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) - { - if (_syncCopy) - { - try - { - _stream.CopyTo(stream, 128); // arbitrary size likely to require multiple read/writes - return Task.CompletedTask; - } - catch (Exception exc) - { - return Task.FromException(exc); - } - } - - return base.SerializeToStreamAsync(stream, context); - } - } - - public static IEnumerable VerifyUploadServersStreamsAndExpectedData - { - get - { - foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) // target server - foreach (bool syncCopy in BoolValues) // force the content copy to happen via Read/Write or ReadAsync/WriteAsync - { - byte[] data = new byte[1234]; - new Random(42).NextBytes(data); - - // A MemoryStream - { - var memStream = new MemoryStream(data, writable: false); - yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(memStream, syncCopy: syncCopy), data }; - } - - // A multipart content that provides its own stream from CreateContentReadStreamAsync - { - var mc = new MultipartContent(); - mc.Add(new ByteArrayContent(data)); - var memStream = new MemoryStream(); - mc.CopyToAsync(memStream).GetAwaiter().GetResult(); - yield return new object[] { remoteServer, mc, memStream.ToArray() }; - } - - // A stream that provides the data synchronously and has a known length - { - var wrappedMemStream = new MemoryStream(data, writable: false); - var syncKnownLengthStream = new DelegateStream( - canReadFunc: () => wrappedMemStream.CanRead, - canSeekFunc: () => wrappedMemStream.CanSeek, - lengthFunc: () => wrappedMemStream.Length, - positionGetFunc: () => wrappedMemStream.Position, - positionSetFunc: p => wrappedMemStream.Position = p, - readFunc: (buffer, offset, count) => wrappedMemStream.Read(buffer, offset, count), - readAsyncFunc: (buffer, offset, count, token) => wrappedMemStream.ReadAsync(buffer, offset, count, token)); - yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncKnownLengthStream, syncCopy: syncCopy), data }; - } - - // A stream that provides the data synchronously and has an unknown length - { - int syncUnknownLengthStreamOffset = 0; - - Func readFunc = (buffer, offset, count) => - { - int bytesRemaining = data.Length - syncUnknownLengthStreamOffset; - int bytesToCopy = Math.Min(bytesRemaining, count); - Array.Copy(data, syncUnknownLengthStreamOffset, buffer, offset, bytesToCopy); - syncUnknownLengthStreamOffset += bytesToCopy; - return bytesToCopy; - }; - - var syncUnknownLengthStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => false, - readFunc: readFunc, - readAsyncFunc: (buffer, offset, count, token) => Task.FromResult(readFunc(buffer, offset, count))); - yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncUnknownLengthStream, syncCopy: syncCopy), data }; - } - - // A stream that provides the data asynchronously - { - int asyncStreamOffset = 0, maxDataPerRead = 100; - - Func readFunc = (buffer, offset, count) => - { - int bytesRemaining = data.Length - asyncStreamOffset; - int bytesToCopy = Math.Min(bytesRemaining, Math.Min(maxDataPerRead, count)); - Array.Copy(data, asyncStreamOffset, buffer, offset, bytesToCopy); - asyncStreamOffset += bytesToCopy; - return bytesToCopy; - }; - - var asyncStream = new DelegateStream( - canReadFunc: () => true, - canSeekFunc: () => false, - readFunc: readFunc, - readAsyncFunc: async (buffer, offset, count, token) => - { - await Task.Delay(1).ConfigureAwait(false); - return readFunc(buffer, offset, count); - }); - yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(asyncStream, syncCopy: syncCopy), data }; - } - - // Providing data from a FormUrlEncodedContent's stream - { - var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair("key", "val") }); - yield return new object[] { remoteServer, formContent, Encoding.GetEncoding("iso-8859-1").GetBytes("key=val") }; - } - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_CallMethod_NullContent(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - string.Empty); - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_CallMethod_EmptyContent(Configuration.Http.RemoteServer remoteServer) - { - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - var content = new StringContent(string.Empty); - using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - string.Empty); - } - } - } - - public static IEnumerable ExpectContinueVersion() - { - return - from expect in new bool?[] {true, false, null} - from version in new Version[] {new Version(1, 0), new Version(1, 1), new Version(2, 0)} - select new object[] {expect, version}; - } - - [OuterLoop("Uses external server")] - [Theory] - [MemberData(nameof(ExpectContinueVersion))] - public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, Version version) - { - // Sync API supported only up to HTTP/1.1 - if (!TestAsync && version.Major >= 2) - { - return; - } - - using (HttpClient client = CreateHttpClient()) - { - var req = new HttpRequestMessage(HttpMethod.Post, version.Major == 2 ? Configuration.Http.Http2RemoteEchoServer : Configuration.Http.RemoteEchoServer) - { - Content = new StringContent("Test String", Encoding.UTF8), - Version = version - }; - req.Headers.ExpectContinue = expectContinue; - - using (HttpResponseMessage response = await client.SendAsync(TestAsync, req)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - if (!IsWinHttpHandler) - { - const string ExpectedReqHeader = "\"Expect\": \"100-continue\""; - if (expectContinue == true && (version >= new Version(1, 1))) - { - Assert.Contains(ExpectedReqHeader, await response.Content.ReadAsStringAsync()); - } - else - { - Assert.DoesNotContain(ExpectedReqHeader, await response.Content.ReadAsStringAsync()); - } - } - } - } - } - - [Fact] - public async Task GetAsync_ExpectContinueTrue_NoContent_StillSendsHeader() - { - if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) - { - return; - } - - if (UseVersion == HttpVersion30) - { - // TODO: ActiveIssue - return; - } - - const string ExpectedContent = "Hello, expecting and continuing world."; - var clientCompleted = new TaskCompletionSource(); - await LoopbackServerFactory.CreateClientAndServerAsync(async uri => - { - using (HttpClient client = CreateHttpClient()) - { - client.DefaultRequestHeaders.ExpectContinue = true; - Assert.Equal(ExpectedContent, await client.GetStringAsync(uri)); - clientCompleted.SetResult(true); - } - }, async server => - { - await server.AcceptConnectionAsync(async connection => - { - HttpRequestData requestData = await connection.ReadRequestDataAsync(); - Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect")); - - await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false); - await connection.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", ExpectedContent.Length.ToString()) }, isFinal: false); - await connection.SendResponseBodyAsync(ExpectedContent); - await clientCompleted.Task; // make sure server closing the connection isn't what let the client complete - }); - }); - } - - public static IEnumerable Interim1xxStatusCode() - { - yield return new object[] { (HttpStatusCode) 100 }; // 100 Continue. - // 101 SwitchingProtocols will be treated as a final status code. - yield return new object[] { (HttpStatusCode) 102 }; // 102 Processing. - yield return new object[] { (HttpStatusCode) 103 }; // 103 EarlyHints. - yield return new object[] { (HttpStatusCode) 150 }; - yield return new object[] { (HttpStatusCode) 180 }; - yield return new object[] { (HttpStatusCode) 199 }; - } - - [Theory] - [MemberData(nameof(Interim1xxStatusCode))] - public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnored(HttpStatusCode responseStatusCode) - { - if (IsWinHttpHandler && UseVersion >= HttpVersion20.Value) - { - return; - } - - if (UseVersion == HttpVersion30) + if (UseVersion == HttpVersion30) { // TODO: ActiveIssue return; @@ -2425,117 +1803,6 @@ await LoopbackServer.CreateServerAsync(async (server, uri) => }); } - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_Redirect_ResultingGetFormattedCorrectly(Configuration.Http.RemoteServer remoteServer) - { - const string ContentString = "This is the content string."; - var content = new StringContent(ContentString); - Uri redirectUri = remoteServer.RedirectUriForDestinationUri( - 302, - remoteServer.EchoUri, - 1); - - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - using (HttpResponseMessage response = await client.PostAsync(redirectUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - string responseContent = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain(ContentString, responseContent); - Assert.DoesNotContain("Content-Length", responseContent); - } - } - - [OuterLoop("Takes several seconds")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_RedirectWith307_LargePayload(Configuration.Http.RemoteServer remoteServer) - { - if (remoteServer.HttpVersion == new Version(2, 0)) - { - // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux - // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix. - // Disable until we can investigate further. - return; - } - - await PostAsync_Redirect_LargePayload_Helper(remoteServer, 307, true); - } - - [OuterLoop("Takes several seconds")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_RedirectWith302_LargePayload(Configuration.Http.RemoteServer remoteServer) - { - await PostAsync_Redirect_LargePayload_Helper(remoteServer, 302, false); - } - - public async Task PostAsync_Redirect_LargePayload_Helper(Configuration.Http.RemoteServer remoteServer, int statusCode, bool expectRedirectToPost) - { - using (var fs = new FileStream( - Path.Combine(Path.GetTempPath(), Path.GetTempFileName()), - FileMode.Create, - FileAccess.ReadWrite, - FileShare.None, - 0x1000, - FileOptions.DeleteOnClose)) - { - string contentString = string.Join("", Enumerable.Repeat("Content", 100000)); - byte[] contentBytes = Encoding.UTF32.GetBytes(contentString); - fs.Write(contentBytes, 0, contentBytes.Length); - fs.Flush(flushToDisk: true); - fs.Position = 0; - - Uri redirectUri = remoteServer.RedirectUriForDestinationUri( - statusCode: statusCode, - destinationUri: remoteServer.VerifyUploadUri, - hops: 1); - var content = new StreamContent(fs); - - // Compute MD5 of request body data. This will be verified by the server when it receives the request. - content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(contentBytes); - - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - using (HttpResponseMessage response = await client.PostAsync(redirectUri, content)) - { - try - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - catch - { - _output.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}"); - throw; - } - - if (expectRedirectToPost) - { - IEnumerable headerValue = response.Headers.GetValues("X-HttpRequest-Method"); - Assert.Equal("POST", headerValue.First()); - } - } - } - } - -#if !NETFRAMEWORK - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(RemoteServersMemberData))] - public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer) - { - const string ContentString = "This is the content string."; - using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer)) - { - var content = new StringContent(ContentString); - for (int i = 0; i < 2; i++) - { - using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Contains(ContentString, await response.Content.ReadAsStringAsync()); - } - } - } - } -#endif - [Theory] [InlineData(HttpStatusCode.MethodNotAllowed, "Custom description")] [InlineData(HttpStatusCode.MethodNotAllowed, "")] @@ -2559,156 +1826,10 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => $"HTTP/1.1 {(int)statusCode} {reasonPhrase}\r\nContent-Length: 0\r\n\r\n")); } -#endregion - -#region Various HTTP Method Tests - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(HttpMethods))] - public async Task SendAsync_SendRequestUsingMethodToEchoServerWithNoContent_MethodCorrectlySent( - string method, - Uri serverUri) - { - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - - using (HttpClient client = CreateHttpClient()) - { - var request = new HttpRequestMessage( - new HttpMethod(method), - serverUri) { Version = UseVersion }; - - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - TestHelper.VerifyRequestMethod(response, method); - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(HttpMethodsThatAllowContent))] - public async Task SendAsync_SendRequestUsingMethodToEchoServerWithContent_Success( - string method, - Uri serverUri) - { - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - - using (HttpClient client = CreateHttpClient()) - { - var request = new HttpRequestMessage( - new HttpMethod(method), - serverUri) { Version = UseVersion }; - request.Content = new StringContent(ExpectedContent); - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - TestHelper.VerifyRequestMethod(response, method); - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - - Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - ExpectedContent); - } - } - } - - [OuterLoop("Uses external server")] - [Theory] - [InlineData("12345678910", 0)] - [InlineData("12345678910", 5)] - public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success(string stringContent, int startingPosition) - { - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - - using (var handler = new HttpMessageInvoker(CreateHttpClientHandler())) - { - byte[] byteContent = Encoding.ASCII.GetBytes(stringContent); - var content = new MemoryStream(); - content.Write(byteContent, 0, byteContent.Length); - content.Position = startingPosition; - var request = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteEchoServer) { Content = new StreamContent(content), Version = UseVersion }; - - for (int iter = 0; iter < 2; iter++) - { - using (HttpResponseMessage response = await handler.SendAsync(TestAsync, request, CancellationToken.None)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - string responseContent = await response.Content.ReadAsStringAsync(); - - Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent); - string bodyContent = System.Text.Json.JsonDocument.Parse(responseContent).RootElement.GetProperty("BodyContent").GetString(); - Assert.Contains(stringContent.Substring(startingPosition), bodyContent); - if (startingPosition != 0) - { - Assert.DoesNotContain(stringContent.Substring(0, startingPosition), bodyContent); - } - } - } - } - } - - [OuterLoop("Uses external server")] - [Theory, MemberData(nameof(HttpMethodsThatDontAllowContent))] - public async Task SendAsync_SendRequestUsingNoBodyMethodToEchoServerWithContent_NoBodySent( - string method, - Uri serverUri) - { - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } + #endregion - using (HttpClient client = CreateHttpClient()) - { - var request = new HttpRequestMessage( - new HttpMethod(method), - serverUri) - { - Content = new StringContent(ExpectedContent), - Version = UseVersion - }; + #region Version tests - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) - { - if (method == "TRACE") - { - // .NET Framework also allows the HttpWebRequest and HttpClient APIs to send a request using 'TRACE' - // verb and a request body. The usual response from a server is "400 Bad Request". - // See here for more info: https://github.com/dotnet/runtime/issues/17475 - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - } - else - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - TestHelper.VerifyRequestMethod(response, method); - string responseContent = await response.Content.ReadAsStringAsync(); - Assert.DoesNotContain(ExpectedContent, responseContent); - } - } - } - } -#endregion - -#region Version tests - [OuterLoop("Uses external server")] [Fact] public async Task SendAsync_RequestVersion10_ServerReceivesVersion10Request() { @@ -2718,31 +1839,17 @@ public async Task SendAsync_RequestVersion10_ServerReceivesVersion10Request() return; } - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 0)); Assert.Equal(new Version(1, 0), receivedRequestVersion); } - [OuterLoop("Uses external server")] [Fact] public async Task SendAsync_RequestVersion11_ServerReceivesVersion11Request() { - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 1)); Assert.Equal(new Version(1, 1), receivedRequestVersion); } - [OuterLoop("Uses external server")] [Fact] public async Task SendAsync_RequestVersionNotSpecified_ServerReceivesVersion11Request() { @@ -2752,54 +1859,50 @@ public async Task SendAsync_RequestVersionNotSpecified_ServerReceivesVersion11Re return; } - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - // The default value for HttpRequestMessage.Version is Version(1,1). // So, we need to set something different (0,0), to test the "unknown" version. Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(0, 0)); Assert.Equal(new Version(1, 1), receivedRequestVersion); } - [OuterLoop("Uses external server")] - [Theory] - [MemberData(nameof(Http2Servers))] - public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(Uri server) + private async Task SendRequestAndGetRequestVersionAsync(Version requestVersion) { - // Sync API supported only up to HTTP/1.1 - if (!TestAsync) - { - return; - } + Version receivedRequestVersion = null; - if (UseVersion == HttpVersion30) + await LoopbackServer.CreateServerAsync(async (server, url) => { - // External servers do not support HTTP3 currently. - return; - } + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Version = requestVersion; - // We don't currently have a good way to test whether HTTP/2 is supported without - // using the same mechanism we're trying to test, so for now we allow both 2.0 and 1.1 responses. - var request = new HttpRequestMessage(HttpMethod.Get, server); - request.Version = new Version(2, 0); + using (HttpClient client = CreateHttpClient()) + { + Task getResponse = client.SendAsync(TestAsync, request); + Task> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(); + await TestHelper.WhenAllCompletedOrAnyFailed(getResponse, serverTask); - using (HttpClient client = CreateHttpClient()) - { - // It is generally expected that the test hosts will be trusted, so we don't register a validation - // callback in the usual case. + List receivedRequest = await serverTask; + using (HttpResponseMessage response = await getResponse) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.True( - response.Version == new Version(2, 0) || - response.Version == new Version(1, 1), - "Response version " + response.Version); + string statusLine = receivedRequest[0]; + if (statusLine.Contains("/1.0")) + { + receivedRequestVersion = new Version(1, 0); + } + else if (statusLine.Contains("/1.1")) + { + receivedRequestVersion = new Version(1, 1); + } + else + { + Assert.True(false, "Invalid HTTP request version"); + } } - } + }); + + return receivedRequestVersion; } [Fact] @@ -2834,75 +1937,6 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => }); } - [OuterLoop("Uses external server")] - [ConditionalTheory(nameof(IsWindows10Version1607OrGreater)), MemberData(nameof(Http2NoPushServers))] - public async Task SendAsync_RequestVersion20_ResponseVersion20(Uri server) - { - // Sync API supported only up to HTTP/1.1 - if (!TestAsync) - { - return; - } - - if (UseVersion == HttpVersion30) - { - // External servers do not support HTTP3 currently. - return; - } - - _output.WriteLine(server.AbsoluteUri.ToString()); - var request = new HttpRequestMessage(HttpMethod.Get, server); - request.Version = new Version(2, 0); - - using (HttpClient client = CreateHttpClient()) - { - using (HttpResponseMessage response = await client.SendAsync(TestAsync, request)) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(new Version(2, 0), response.Version); - } - } - } - - private async Task SendRequestAndGetRequestVersionAsync(Version requestVersion) - { - Version receivedRequestVersion = null; - - await LoopbackServer.CreateServerAsync(async (server, url) => - { - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Version = requestVersion; - - using (HttpClient client = CreateHttpClient()) - { - Task getResponse = client.SendAsync(TestAsync, request); - Task> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(); - await TestHelper.WhenAllCompletedOrAnyFailed(getResponse, serverTask); - - List receivedRequest = await serverTask; - using (HttpResponseMessage response = await getResponse) - { - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - string statusLine = receivedRequest[0]; - if (statusLine.Contains("/1.0")) - { - receivedRequestVersion = new Version(1, 0); - } - else if (statusLine.Contains("/1.1")) - { - receivedRequestVersion = new Version(1, 1); - } - else - { - Assert.True(false, "Invalid HTTP request version"); - } - } - }); - - return receivedRequestVersion; - } #endregion #region Uri wire transmission encoding tests @@ -2939,7 +1973,7 @@ await LoopbackServerFactory.CreateServerAsync(async (server, rootUrl) => [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux), nameof(PlatformDetection.IsNotBrowserDomSupported))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/18258")] public async Task GetAsync_InvalidUrl_ExpectedExceptionThrown() { - string invalidUri = $"http://{Configuration.Sockets.InvalidHost}"; + string invalidUri = $"http://nosuchhost.invalid"; _output.WriteLine($"{DateTime.Now} connecting to {invalidUri}"); using (HttpClient client = CreateHttpClient()) { diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index c8ca1bcadbc84..26a084b969f20 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -2,21 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +#if !NETCOREAPP using System.Diagnostics; +#endif using System.IO; -using System.Net.Test.Common; using System.Threading; using System.Threading.Tasks; -using System.Reflection; using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { using Configuration = System.Net.Test.Common.Configuration; - public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase { + // This file is shared with the WinHttpHandler implementation, which supports .NET Framework + // So, define this so derived tests can use it. public static readonly Version HttpVersion30 = new Version(3, 0); public readonly ITestOutputHelper _output; diff --git a/src/libraries/Common/tests/System/Net/WebSockets/WebSocketCreateTest.cs b/src/libraries/Common/tests/System/Net/WebSockets/WebSocketCreateTest.cs index f22a8add7d96a..7f391f7754ec6 100644 --- a/src/libraries/Common/tests/System/Net/WebSockets/WebSocketCreateTest.cs +++ b/src/libraries/Common/tests/System/Net/WebSockets/WebSocketCreateTest.cs @@ -40,6 +40,7 @@ public void CreateFromStream_ValidBufferSizes_CreatesWebSocket() [OuterLoop("Uses external servers")] [Theory] [MemberData(nameof(EchoServers))] + [PlatformSpecific(~TestPlatforms.Browser)] // System.Net.Sockets is not supported on this platform. public async Task WebSocketProtocol_CreateFromConnectedStream_CanSendReceiveData(Uri echoUri) { if (PlatformDetection.IsWindows7) @@ -203,6 +204,7 @@ public async Task ReceiveAsync_ServerSplitHeader_ValidDataReceived() [OuterLoop("Uses external servers")] [Theory] [MemberData(nameof(EchoServersAndBoolean))] + [PlatformSpecific(~TestPlatforms.Browser)] // System.Net.Sockets is not supported on this platform. public async Task WebSocketProtocol_CreateFromConnectedStream_CloseAsyncClosesStream(Uri echoUri, bool explicitCloseAsync) { if (PlatformDetection.IsWindows7) @@ -244,6 +246,7 @@ public async Task WebSocketProtocol_CreateFromConnectedStream_CloseAsyncClosesSt [OuterLoop("Uses external servers")] [Theory] [MemberData(nameof(EchoServersAndBoolean))] + [PlatformSpecific(~TestPlatforms.Browser)] // System.Net.Sockets is not supported on this platform. public async Task WebSocketProtocol_CreateFromConnectedStream_CloseAsyncAfterCloseReceivedClosesStream(Uri echoUri, bool useCloseOutputAsync) { using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) diff --git a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs index ba28f79a04ec2..8c672e9d21e9e 100644 --- a/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs +++ b/src/libraries/Common/tests/TestUtilities/System/Buffers/BoundedMemory.Windows.cs @@ -276,7 +276,7 @@ private struct MEMORY_BASIC_INFORMATION private sealed class VirtualAllocHandle : SafeHandle { // Called by P/Invoke when returning SafeHandles - private VirtualAllocHandle() + public VirtualAllocHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ReadMe.SharedCode.md b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ReadMe.SharedCode.md index cd18bcb663f68..4d0ba2616a800 100644 --- a/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ReadMe.SharedCode.md +++ b/src/libraries/Common/tests/Tests/System/Net/aspnetcore/ReadMe.SharedCode.md @@ -2,4 +2,4 @@ The code in this directory is shared between the runtime libraries and AspNetCor For additional details see: - runtime/src/libraries/Common/src/System/Net/Http/aspnetcore/ReadMe.SharedCode.md -- AspNetCore/src/Shared/runtime/ReadMe.SharedCode.md +- aspnetcore/src/Shared/runtime/ReadMe.SharedCode.md diff --git a/src/libraries/Common/tests/Tests/System/StringTests.cs b/src/libraries/Common/tests/Tests/System/StringTests.cs index 8ea5bbac4b0ca..305c0efc6b450 100644 --- a/src/libraries/Common/tests/Tests/System/StringTests.cs +++ b/src/libraries/Common/tests/Tests/System/StringTests.cs @@ -317,7 +317,7 @@ void Validate(string result) Validate(string.Concat((IEnumerable)values)); // Call the generic IEnumerable-based overload } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // mini-stress test that likely runs for several seconds public static void Concat_String_ConcurrencySafe() { diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index dbd416980ad80..3daf1a941bec4 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -95,9 +95,6 @@ Open MicrosoftAspNetCore - - - false diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/IDistributedCache.cs b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/IDistributedCache.cs index 6b38f4665100f..d3b32ec54c03e 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/IDistributedCache.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/IDistributedCache.cs @@ -47,7 +47,7 @@ public interface IDistributedCache /// /// Refreshes a value in the cache based on its key, resetting its sliding expiration timeout (if any). /// - /// A string identifying the requested calue. + /// A string identifying the requested value. void Refresh(string key); /// diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs index 67484a6af18d7..20cb79c69fbf7 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs @@ -2,9 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Collections.Concurrent; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; +using System.Diagnostics; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { @@ -55,26 +58,39 @@ protected override object VisitConstructor(ConstructorCallSite constructorCallSi #endif } - protected override object VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) + protected override object VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context) { - return VisitCache(singletonCallSite, context, context.Scope.Engine.Root, RuntimeResolverLock.Root); + var lockType = RuntimeResolverLock.Root; + bool lockTaken = false; + + // using more granular locking (per singleton) for the root + Monitor.Enter(callSite, ref lockTaken); + try + { + return ResolveService(callSite, context, lockType, serviceProviderEngine: context.Scope.Engine.Root); + } + finally + { + if (lockTaken) + { + Monitor.Exit(callSite); + } + } } - protected override object VisitScopeCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context) + protected override object VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context) { // Check if we are in the situation where scoped service was promoted to singleton // and we need to lock the root - RuntimeResolverLock requiredScope = context.Scope == context.Scope.Engine.Root ? - RuntimeResolverLock.Root : - RuntimeResolverLock.Scope; - - return VisitCache(singletonCallSite, context, context.Scope, requiredScope); + return context.Scope == context.Scope.Engine.Root ? + VisitRootCache(callSite, context) : + VisitCache(callSite, context, context.Scope, RuntimeResolverLock.Scope); } private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType) { bool lockTaken = false; - System.Collections.Generic.Dictionary resolvedServices = serviceProviderEngine.ResolvedServices; + IDictionary resolvedServices = serviceProviderEngine.ResolvedServices; // Taking locks only once allows us to fork resolution process // on another thread without causing the deadlock because we @@ -87,19 +103,7 @@ private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext conte try { - if (!resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved)) - { - resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext - { - Scope = serviceProviderEngine, - AcquiredLocks = context.AcquiredLocks | lockType - }); - - serviceProviderEngine.CaptureDisposable(resolved); - resolvedServices.Add(callSite.Cache.Key, resolved); - } - - return resolved; + return ResolveService(callSite, context, lockType, serviceProviderEngine); } finally { @@ -110,6 +114,32 @@ private object VisitCache(ServiceCallSite callSite, RuntimeResolverContext conte } } + private object ResolveService(ServiceCallSite callSite, RuntimeResolverContext context, RuntimeResolverLock lockType, ServiceProviderEngineScope serviceProviderEngine) + { + IDictionary resolvedServices = serviceProviderEngine.ResolvedServices; + + // Note: This method has already taken lock by the caller for resolution and access synchronization. + // For root: uses a concurrent dictionary and takes a per singleton lock for resolution. + // For scoped: takes a dictionary as both a resolution lock and a dictionary access lock. + Debug.Assert( + (lockType == RuntimeResolverLock.Root && resolvedServices is ConcurrentDictionary) || + (lockType == RuntimeResolverLock.Scope && Monitor.IsEntered(resolvedServices))); + + if (resolvedServices.TryGetValue(callSite.Cache.Key, out object resolved)) + { + return resolved; + } + + resolved = VisitCallSiteMain(callSite, new RuntimeResolverContext + { + Scope = serviceProviderEngine, + AcquiredLocks = context.AcquiredLocks | lockType + }); + serviceProviderEngine.CaptureDisposable(resolved); + resolvedServices.Add(callSite.Cache.Key, resolved); + return resolved; + } + protected override object VisitConstant(ConstantCallSite constantCallSite, RuntimeResolverContext context) { return constantCallSite.DefaultValue; diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs index ca33439284172..d6d804c648448 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngine.cs @@ -19,7 +19,7 @@ internal abstract class ServiceProviderEngine : IServiceProviderEngine, IService protected ServiceProviderEngine(IEnumerable serviceDescriptors) { _createServiceAccessor = CreateServiceAccessor; - Root = new ServiceProviderEngineScope(this); + Root = new ServiceProviderEngineScope(this, isRoot: true); RuntimeResolver = new CallSiteRuntimeResolver(); CallSiteFactory = new CallSiteFactory(serviceDescriptors); CallSiteFactory.Add(typeof(IServiceProvider), new ServiceProviderCallSite()); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs index 48335806c9e06..76988982c8754 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.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.Diagnostics; using System.Threading.Tasks; @@ -19,12 +20,15 @@ internal class ServiceProviderEngineScope : IServiceScope, IServiceProvider, IAs private bool _disposed; private readonly object _disposelock = new object(); - public ServiceProviderEngineScope(ServiceProviderEngine engine) + public ServiceProviderEngineScope(ServiceProviderEngine engine, bool isRoot = false) { Engine = engine; + + // To reduce lock contention for singletons upon resolve we use a concurrent dictionary. + ResolvedServices = isRoot ? new ConcurrentDictionary() : new Dictionary(); } - internal Dictionary ResolvedServices { get; } = new Dictionary(); + internal IDictionary ResolvedServices { get; } public ServiceProviderEngine Engine { get; } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs index d83a52d4f9e19..8b8240c5d0b02 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/tests/DI.Tests/ServiceProviderContainerTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.Fakes; @@ -406,6 +407,303 @@ public void Dispose() } } + [ThreadStatic] + public static int ThreadId; + + private class OuterSingleton + { + public InnerSingleton InnerSingleton; + public OuterSingleton(InnerSingleton innerSingleton) + { + InnerSingleton = innerSingleton; + } + } + + private class InnerSingleton + { + public InnerSingleton(ManualResetEvent mre1, ManualResetEvent mre2) + { + // Making sure ctor gets called only once + Assert.True(!mre1.WaitOne(0) && !mre2.WaitOne(0)); + + // Then use mre2 to signal execution reached this ctor call + mre2.Set(); + + // Wait until it's OK to leave ctor + mre1.WaitOne(); + } + } + + [Fact] + public async Task GetRequiredService_ResolvingSameSingletonInTwoThreads_SameServiceReturned() + { + using (var mreForThread1 = new ManualResetEvent(false)) + using (var mreForThread2 = new ManualResetEvent(false)) + using (var mreForThread3 = new ManualResetEvent(false)) + { + InnerSingleton innerSingleton = null; + OuterSingleton outerSingleton = null; + IServiceProvider sp = null; + + // Arrange + var services = new ServiceCollection(); + + services.AddSingleton(); + services.AddSingleton(sp => new InnerSingleton(mreForThread1, mreForThread2)); + + sp = services.BuildServiceProvider(); + + var t1 = Task.Run(() => + { + outerSingleton = sp.GetRequiredService(); + }); + + // Wait until mre2 gets set in InnerSingleton ctor + mreForThread2.WaitOne(); + + var t2 = Task.Run(() => + { + mreForThread3.Set(); + + // This waits on InnerSingleton singleton lock that is taken in thread 1 + innerSingleton = sp.GetRequiredService(); + }); + + mreForThread3.WaitOne(); + + // Set a timeout before unblocking execution of both thread1 and thread2 via mre1: + Assert.False(mreForThread1.WaitOne(10)); + + // By this time thread 1 has already reached InnerSingleton ctor and is waiting for mre1. + // within the GetRequiredService call, thread 2 should be waiting on a singleton lock for InnerSingleton + // (rather than trying to instantiating InnerSingleton twice). + mreForThread1.Set(); + + // Act + await t1; + await t2; + + // Assert + Assert.NotNull(outerSingleton); + Assert.NotNull(innerSingleton); + Assert.Same(outerSingleton.InnerSingleton, innerSingleton); + } + } + + [Fact] + public async Task GetRequiredService_UsesSingletonAndLazyLocks_NoDeadlock() + { + using (var mreForThread1 = new ManualResetEvent(false)) + using (var mreForThread2 = new ManualResetEvent(false)) + { + // Thread 1: Thing1 (transient) -> Thing0 (singleton) + // Thread 2: Thing2 (singleton) -> Thing1 (transient) -> Thing0 (singleton) + + // 1. Thread 1 resolves the Thing1 which is a transient service + // 2. In parallel, Thread 2 resolves Thing2 which is a singleton + // 3. Thread 1 enters the factory callback for Thing1 and takes the lazy lock + // 4. Thread 2 takes callsite for Thing2 as a singleton lock when it resolves Thing2 + // 5. Thread 2 enters the factory callback for Thing1 and waits on the lazy lock + // 6. Thread 1 calls GetRequiredService on the service provider, takes callsite for Thing0 causing no deadlock + // (rather than taking the locks that are already taken - either the lazy lock or the Thing2 callsite lock) + + Thing0 thing0 = null; + Thing1 thing1 = null; + Thing2 thing2 = null; + IServiceProvider sp = null; + var sb = new StringBuilder(); + + // Arrange + var services = new ServiceCollection(); + + var lazy = new Lazy(() => + { + sb.Append("3"); + mreForThread2.Set(); // Now that thread 1 holds lazy lock, allow thread 2 to continue + + // by this time, Thread 2 is holding a singleton lock for Thing2, + // and Thread one holds the lazy lock + // the call below to resolve Thing0 does not hang + // since singletons do not share the same lock upon resolve anymore. + thing0 = sp.GetRequiredService(); + return new Thing1(thing0); + }); + + services.AddSingleton(); + services.AddTransient(sp => + { + if (ThreadId == 2) + { + sb.Append("1"); + mreForThread1.Set(); // [b] Allow thread 1 to continue execution and take the lazy lock + mreForThread2.WaitOne(); // [c] Wait until thread 1 takes the lazy lock + + sb.Append("4"); + } + + // Let Thread 1 over take Thread 2 + Thing1 value = lazy.Value; + return value; + }); + services.AddSingleton(); + + sp = services.BuildServiceProvider(); + + var t1 = Task.Run(() => + { + ThreadId = 1; + using var scope1 = sp.CreateScope(); + mreForThread1.WaitOne(); // [a] Waits until thread 2 reaches the transient call to ensure it holds Thing2 singleton lock + + sb.Append("2"); + thing1 = scope1.ServiceProvider.GetRequiredService(); + }); + + var t2 = Task.Run(() => + { + ThreadId = 2; + using var scope2 = sp.CreateScope(); + thing2 = scope2.ServiceProvider.GetRequiredService(); + }); + + // Act + await t1; + await t2; + + // Assert + Assert.NotNull(thing0); + Assert.NotNull(thing1); + Assert.NotNull(thing2); + Assert.Equal("1234", sb.ToString()); // Expected order of execution + } + } + + [Fact] + public async Task GetRequiredService_BiggerObjectGraphWithOpenGenerics_NoDeadlock() + { + // Test is similar to GetRequiredService_UsesSingletonAndLazyLocks_NoDeadlock (but for open generics and a larger object graph) + using (var mreForThread1 = new ManualResetEvent(false)) + using (var mreForThread2 = new ManualResetEvent(false)) + { + // Arrange + List> constrainedThing4Services = null; + List> constrainedThing5Services = null; + + Thing3 thing3 = null; + IServiceProvider sp = null; + var sb = new StringBuilder(); + + var services = new ServiceCollection(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); + + var lazy = new Lazy(() => + { + sb.Append("3"); + mreForThread2.Set(); // Now that thread 1 holds lazy lock, allow thread 2 to continue + + thing3 = sp.GetRequiredService(); + return new Thing4(thing3); + }); + + services.AddTransient(sp => + { + if (ThreadId == 2) + { + sb.Append("1"); + mreForThread1.Set(); // [b] Allow thread 1 to continue execution and take the lazy lock + mreForThread2.WaitOne(); // [c] Wait until thread 1 takes the lazy lock + + sb.Append("4"); + } + + // Let Thread 1 over take Thread 2 + Thing4 value = lazy.Value; + return value; + }); + services.AddSingleton(); + + sp = services.BuildServiceProvider(); + + // Act + var t1 = Task.Run(() => + { + ThreadId = 1; + using var scope1 = sp.CreateScope(); + mreForThread1.WaitOne(); // Waits until thread 2 reaches the transient call to ensure it holds Thing4 singleton lock + + sb.Append("2"); + constrainedThing4Services = sp.GetServices>().ToList(); + }); + + var t2 = Task.Run(() => + { + ThreadId = 2; + using var scope2 = sp.CreateScope(); + constrainedThing5Services = sp.GetServices>().ToList(); + }); + + // Act + await t1; + await t2; + + Assert.Equal("1234", sb.ToString()); // Expected order of execution + + var thing4 = sp.GetRequiredService(); + var thing5 = sp.GetRequiredService(); + + // Assert + Assert.NotNull(thing3); + Assert.NotNull(thing4); + Assert.NotNull(thing5); + Assert.Equal(1, constrainedThing4Services.Count); + Assert.Equal(1, constrainedThing5Services.Count); + Assert.Same(thing4, constrainedThing4Services[0].Value); + Assert.Same(thing5, constrainedThing5Services[0].Value); + } + } + + private class Thing5 + { + public Thing5(Thing4 thing) + { + } + } + + private class Thing4 + { + public Thing4(Thing3 thing) + { + } + } + + private class Thing3 + { + public Thing3(Thing2 thing) + { + } + } + + private class Thing2 + { + public Thing2(Thing1 thing1) + { + } + } + + private class Thing1 + { + public Thing1(Thing0 thing0) + { + } + } + + private class Thing0 { } + [ActiveIssue("https://github.com/dotnet/runtime/issues/42160")] // We don't support value task services currently [Theory] [InlineData(ServiceLifetime.Transient)] @@ -454,7 +752,6 @@ public void GenericIEnumerableItemCachedInTheRightSlot() Assert.Same(serviceRef1, servicesRef1); } - [Fact] public async Task ProviderDisposeAsyncCallsDisposeAsyncOnServices() { @@ -689,5 +986,62 @@ public async ValueTask DisposeAsync() DisposeCount++; } } + + [Fact] + public async Task GetRequiredService_ResolveUniqueServicesConcurrently_StressTestSuccessful() + { + for (int i = 0; i < 100; i++) + { + Assert.True(await ResolveUniqueServicesConcurrently()); + } + } + + private async Task ResolveUniqueServicesConcurrently() + { + var types = new Type[] + { + typeof(A), typeof(B), typeof(C), typeof(D), typeof(E), + typeof(F), typeof(G), typeof(H), typeof(I), typeof(J) + }; + + IServiceProvider sp = null; + var services = new ServiceCollection(); + foreach (var type in types) + { + services.AddSingleton(type); + } + + sp = services.BuildServiceProvider(); + var tasks = new List>(); + foreach (var type in types) + { + tasks.Add(Task.Run(() => + sp.GetRequiredService(type) != null) + ); + } + + await Task.WhenAll(tasks); + bool succeeded = true; + foreach (var task in tasks) + { + if (!task.Result) + { + succeeded = false; + break; + } + } + return succeeded; + } + + private class A { } + private class B { } + private class C { } + private class D { } + private class E { } + private class F { } + private class G { } + private class H { } + private class I { } + private class J { } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs index 2d9fa977ee725..7007782a7a2a2 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerMessage.cs @@ -110,7 +110,7 @@ public static Func DefineScopeThe type of the third parameter passed to the named format string. /// The type of the fourth parameter passed to the named format string. /// The type of the fifth parameter passed to the named format string. - /// The type of the sisxth parameter passed to the named format string. + /// The type of the sixth parameter passed to the named format string. /// The named format string /// A delegate which when invoked creates a log scope. public static Func DefineScope(string formatString) diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs index 0d5ddc6931a13..5ffb956f284c1 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/StringSegment.cs @@ -146,24 +146,20 @@ public char this[int index] /// public static int Compare(StringSegment a, StringSegment b, StringComparison comparisonType) { - int minLength = Math.Min(a.Length, b.Length); - int diff = string.Compare(a.Buffer, a.Offset, b.Buffer, b.Offset, minLength, comparisonType); - if (diff == 0) + if (a.HasValue && b.HasValue) { - diff = a.Length - b.Length; + return a.AsSpan().CompareTo(b.AsSpan(), comparisonType); + } + else + { + CheckStringComparison(comparisonType); // must arg check before returning + return !a.HasValue ? (b.HasValue ? -1 : 0) : 1; // null sorts less than non-null, and two nulls sort as equal } - - return diff; } /// public override bool Equals(object obj) { - if (obj is null) - { - return false; - } - return obj is StringSegment segment && Equals(segment); } @@ -182,12 +178,15 @@ public override bool Equals(object obj) /// if the current object is equal to the other parameter; otherwise, . public bool Equals(StringSegment other, StringComparison comparisonType) { - if (Length != other.Length) + if (HasValue && other.HasValue) { - return false; + return AsSpan().Equals(other.AsSpan(), comparisonType); + } + else + { + CheckStringComparison(comparisonType); // must arg check before returning + return !HasValue && !other.HasValue; // only return true if both are null } - - return string.Compare(Buffer, Offset, other.Buffer, other.Offset, other.Length, comparisonType) == 0; } // This handles StringSegment.Equals(string, StringSegment, StringComparison) and StringSegment.Equals(StringSegment, string, StringComparison) @@ -232,20 +231,20 @@ public bool Equals(string text, StringComparison comparisonType) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); } - int textLength = text.Length; - if (!HasValue || Length != textLength) + if (!HasValue) { + CheckStringComparison(comparisonType); // must arg check before returning return false; } - return string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0; + return AsSpan().Equals(text.AsSpan(), comparisonType); } /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { -#if NETCOREAPP || NETSTANDARD2_1 +#if NETCOREAPP return string.GetHashCode(AsSpan()); #elif (NETSTANDARD2_0 || NETFRAMEWORK) // This GetHashCode is expensive since it allocates on every call. @@ -255,7 +254,6 @@ public override int GetHashCode() #else #error Target frameworks need to be updated. #endif - } /// @@ -310,15 +308,13 @@ public bool StartsWith(string text, StringComparison comparisonType) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); } - bool result = false; - int textLength = text.Length; - - if (HasValue && Length >= textLength) + if (!HasValue) { - result = string.Compare(Buffer, Offset, text, 0, textLength, comparisonType) == 0; + CheckStringComparison(comparisonType); // must arg check before returning + return false; } - return result; + return AsSpan().StartsWith(text.AsSpan(), comparisonType); } /// @@ -338,16 +334,13 @@ public bool EndsWith(string text, StringComparison comparisonType) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); } - bool result = false; - int textLength = text.Length; - int comparisonLength = Offset + Length - textLength; - - if (HasValue && comparisonLength > 0) + if (!HasValue) { - result = string.Compare(Buffer, comparisonLength, text, 0, textLength, comparisonType) == 0; + CheckStringComparison(comparisonType); // must arg check before returning + return false; } - return result; + return AsSpan().EndsWith(text.AsSpan(), comparisonType); } /// @@ -445,10 +438,10 @@ public int IndexOf(char c, int start, int count) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count); } - int index = Buffer.IndexOf(c, offset, count); - if (index != -1) + int index = AsSpan().Slice(start, count).IndexOf(c); + if (index >= 0) { - index -= Offset; + index += start; } return index; @@ -552,18 +545,7 @@ public int IndexOfAny(char[] anyOf) /// The zero-based index position of value if that character is found, or -1 if it is not. public int LastIndexOf(char value) { - int index = -1; - - if (HasValue) - { - index = Buffer.LastIndexOf(value, Offset + Length - 1, Length); - if (index != -1) - { - index -= Offset; - } - } - - return index; + return AsSpan().LastIndexOf(value); } /// @@ -576,54 +558,40 @@ public int LastIndexOf(char value) /// Removes all leading whitespaces. /// /// The trimmed . - public unsafe StringSegment TrimStart() + public StringSegment TrimStart() { - int trimmedStart = Offset; - int length = Offset + Length; + ReadOnlySpan span = AsSpan(); - fixed (char* p = Buffer) + int i; + for (i = 0; i < span.Length; i++) { - while (trimmedStart < length) + if (!char.IsWhiteSpace(span[i])) { - char c = p[trimmedStart]; - - if (!char.IsWhiteSpace(c)) - { - break; - } - - trimmedStart++; + break; } } - return new StringSegment(Buffer, trimmedStart, length - trimmedStart); + return Subsegment(i); } /// /// Removes all trailing whitespaces. /// /// The trimmed . - public unsafe StringSegment TrimEnd() + public StringSegment TrimEnd() { - int offset = Offset; - int trimmedEnd = offset + Length - 1; + ReadOnlySpan span = AsSpan(); - fixed (char* p = Buffer) + int i; + for (i = span.Length - 1; i >= 0; i--) { - while (trimmedEnd >= offset) + if (!char.IsWhiteSpace(span[i])) { - char c = p[trimmedEnd]; - - if (!char.IsWhiteSpace(c)) - { - break; - } - - trimmedEnd--; + break; } } - return new StringSegment(Buffer, offset, trimmedEnd - offset + 1); + return Subsegment(0, i + 1); } /// @@ -664,6 +632,15 @@ public override string ToString() return Value ?? string.Empty; } + private static void CheckStringComparison(StringComparison comparisonType) + { + // Single comparison to check if comparisonType is within [CurrentCulture .. OrdinalIgnoreCase] + if ((uint)comparisonType > (uint)StringComparison.OrdinalIgnoreCase) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.comparisonType); + } + } + // Methods that do no return (i.e. throw) are not inlined // https://github.com/dotnet/coreclr/pull/6103 private static void ThrowInvalidArguments(string buffer, int offset, int length) @@ -716,5 +693,14 @@ Exception GetInvalidArgumentsException(bool hasValue) return ThrowHelper.GetArgumentException(ExceptionResource.Argument_InvalidOffsetLengthStringSegment); } } + + /// + bool IEquatable.Equals(string other) + { + // Explicit interface implementation for IEquatable because + // the interface's Equals method allows null strings, which we return + // as not-equal. + return other != null && Equals(other); + } } } diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs b/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs index 650f8ab963352..8b8a0e4d37ce0 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/src/ThrowHelper.cs @@ -83,7 +83,8 @@ internal enum ExceptionArgument index, value, capacity, - separators + separators, + comparisonType } internal enum ExceptionResource diff --git a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs index 3c7c941c338a2..c3031583407d6 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs +++ b/src/libraries/Microsoft.Extensions.Primitives/tests/StringSegmentTest.cs @@ -271,6 +271,27 @@ public void StringSegment_EndsWith_Invalid() Assert.False(result); } + [Fact] + public void StringSegment_EndsWith_NullString_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("text", () => segment.EndsWith((string)null, StringComparison.Ordinal)); + } + + [Fact] + public void StringSegment_EndsWith_String_InvalidComparisonType_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("comparisonType", () => segment.EndsWith(string.Empty, (StringComparison)(-1))); + Assert.Throws("comparisonType", () => segment.EndsWith(string.Empty, (StringComparison)6)); + } + public static TheoryData StartsWithData { get @@ -319,6 +340,27 @@ public void StringSegment_StartsWith_Invalid() Assert.False(result); } + [Fact] + public void StringSegment_StartsWith_NullString_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("text", () => segment.StartsWith((string)null, StringComparison.Ordinal)); + } + + [Fact] + public void StringSegment_StartsWith_String_InvalidComparisonType_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("comparisonType", () => segment.StartsWith(string.Empty, (StringComparison)(-1))); + Assert.Throws("comparisonType", () => segment.StartsWith(string.Empty, (StringComparison)6)); + } + public static TheoryData EqualsStringData { get @@ -346,6 +388,39 @@ public void StringSegment_Equals_String_Valid(string candidate, StringComparison Assert.Equal(expectedResult, result); } + [Fact] + public void StringSegment_Equals_NullString_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("text", () => segment.Equals((string)null)); + Assert.Throws("text", () => segment.Equals((string)null, StringComparison.Ordinal)); + } + + [Fact] + public void StringSegment_Equals_String_InvalidComparisonType_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("comparisonType", () => segment.Equals("Hello!", (StringComparison)(-1))); + Assert.Throws("comparisonType", () => segment.Equals("Hello!", (StringComparison)6)); + } + + [Fact] + public void StringSegment_Equals_StringSegment_InvalidComparisonType_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("comparisonType", () => segment.Equals(new StringSegment(), (StringComparison)(-1))); + Assert.Throws("comparisonType", () => segment.Equals(new StringSegment(), (StringComparison)6)); + } + [Fact] public void StringSegment_EqualsObject_Valid() { @@ -847,6 +922,17 @@ public void StringSegment_Compare_Greater(StringSegment candidate, StringSegment Assert.True(result > 0, $"{segment} should be greater than {candidate}"); } + [Fact] + public void StringSegment_Compare_InvalidComparisonType_Throws() + { + // Arrange + var segment = new StringSegment(); + + // Act & assert + Assert.Throws("comparisonType", () => StringSegment.Compare(segment, segment, (StringComparison)(-1))); + Assert.Throws("comparisonType", () => StringSegment.Compare(segment, segment, (StringComparison)6)); + } + [Theory] [MemberData(nameof(GetHashCode_ReturnsSameValueForEqualSubstringsData))] public void StringSegmentComparerOrdinal_GetHashCode_ReturnsSameValueForEqualSubstrings(StringSegment segment1, StringSegment segment2) @@ -1169,5 +1255,180 @@ public void TrimEnd_RemovesTrailingWhitespaces(string value, int start, int leng // Assert Assert.Equal(expected, actual.Value); } + + public static TheoryData GlobalizationCompareTestData + { + get + { + return new() + { + { null, string.Empty, StringComparison.Ordinal, -1 }, // null always compares before non-null + { null, string.Empty, StringComparison.InvariantCultureIgnoreCase, -1 }, // null always compares before non-null + { null, null, StringComparison.Ordinal, 0 }, + { null, null, StringComparison.InvariantCultureIgnoreCase, 0 }, + { string.Empty, null, StringComparison.Ordinal, 1 }, + { string.Empty, null, StringComparison.InvariantCultureIgnoreCase, 1 }, + { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCulture, -1 }, // linguistic: lowercase sorts before uppercase + { "x\u00E9y", "xE\u0301y", StringComparison.InvariantCultureIgnoreCase, 0 }, // equal (linguistic, one is normalized) + { "Hello", "HELLO", StringComparison.InvariantCulture, -1 }, // linguistic: lowercase sorts before uppercase + { "Hello", "HELLO", StringComparison.InvariantCultureIgnoreCase, 0 }, + }; + } + } + + [Theory] + [MemberData(nameof(GlobalizationCompareTestData))] + public void StringSegment_CompareEqual_Globalized(string a, string b, StringComparison comparisonType, int expectedCompareToSign) + { + // quick sanity check: run the parameters against the normal string functions to ensure our test data is valid + int returnedSign = string.Compare(a, b, comparisonType); + Assert.Equal(expectedCompareToSign, Math.Sign(returnedSign)); + + StringSegment sa = MakePaddedStringSegment(a); + StringSegment sb = MakePaddedStringSegment(b); + + // StringSegment.Compare + { + returnedSign = StringSegment.Compare(sa, sb, comparisonType); + Assert.Equal(expectedCompareToSign, Math.Sign(returnedSign)); + } + + // StringSegment.Equals(StringSegment, ...) and op_Equality + { + bool areEqual = StringSegment.Equals(sa, sb, comparisonType); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + areEqual = sa.Equals(sb, comparisonType); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + if (comparisonType == StringComparison.Ordinal) + { + areEqual = sa.Equals(sb); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + areEqual = sa.Equals((object)sb); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + areEqual = (sa == sb); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + areEqual = !(sa != sb); + Assert.Equal(expectedCompareToSign == 0, areEqual); + } + } + + // StringSegment.Equals(string, ...) and IEquatable.Equals + { + if (b == null) + { + Assert.False(((IEquatable)sa).Equals(b)); // null string never equal, not even to null StringSegment + } + else + { + bool areEqual = sa.Equals(b, comparisonType); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + if (comparisonType == StringComparison.Ordinal) + { + areEqual = sa.Equals(b); + Assert.Equal(expectedCompareToSign == 0, areEqual); + + areEqual = ((IEquatable)sa).Equals(b); + Assert.Equal(expectedCompareToSign == 0, areEqual); + } + } + } + } + + public static TheoryData GlobalizationStartsWithData + { + get + { + return new() + { + { null, "\u200d", StringComparison.Ordinal, false }, // null never starts with anything + { null, "\u200d", StringComparison.InvariantCulture, false }, // null never starts with anything + { null, string.Empty, StringComparison.Ordinal, false }, // null never starts with anything + { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.InvariantCulture, true }, // linguistic: ZWJ is zero-weight, occurs at all indices + { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially start with the empty string + { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, true }, // linguistic: ZWJ is zero-weight + { "Hello", "h", StringComparison.Ordinal, false }, + { "Hello", "h", StringComparison.OrdinalIgnoreCase, true }, + { "Hello", "hi", StringComparison.Ordinal, false }, + { "Hello", "hi", StringComparison.OrdinalIgnoreCase, false }, + }; + } + } + + [Theory] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "netfx has some IsPrefix / IsSuffix globalization bugs.")] + [MemberData(nameof(GlobalizationStartsWithData))] + public void StringSegment_StartsWith_Globalized(string a, string b, StringComparison comparisonType, bool expectedResult) + { + // quick sanity check: run the parameters against the normal string functions to ensure our test data is valid + if (a != null) + { + Assert.Equal(expectedResult, a.StartsWith(b, comparisonType)); + } + + // Arrange + StringSegment sa = MakePaddedStringSegment(a); + + // Act + bool actualResult = sa.StartsWith(b, comparisonType); + + // Assert + Assert.Equal(expectedResult, actualResult); + } + + public static TheoryData GlobalizationEndsWithData + { + get + { + return new() + { + { null, "\u200d", StringComparison.Ordinal, false }, // null never ends with anything + { null, "\u200d", StringComparison.InvariantCulture, false }, // null never ends with anything + { null, string.Empty, StringComparison.Ordinal, false }, // null never ends with anything + { string.Empty, string.Empty, StringComparison.Ordinal, true }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.Ordinal, false }, // not char-for-char equivalent + { string.Empty, "\u200d", StringComparison.InvariantCulture, true }, // linguistic: ZWJ is zero-weight, occurs at all indices + { "\u200d", string.Empty, StringComparison.Ordinal, true }, // all strings trivially ends with the empty string + { "\u200d", "\u200d\u200d", StringComparison.InvariantCulture, true }, // linguistic: ZWJ is zero-weight + { "HELLO", "o", StringComparison.Ordinal, false }, + { "HELLO", "o", StringComparison.OrdinalIgnoreCase, true }, + { "HELLO", "illo", StringComparison.Ordinal, false }, + { "HELLO", "illo", StringComparison.OrdinalIgnoreCase, false }, + }; + } + } + + [Theory] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "netfx has some IsPrefix / IsSuffix globalization bugs.")] + [MemberData(nameof(GlobalizationEndsWithData))] + public void StringSegment_EndsWith_Globalized(string a, string b, StringComparison comparisonType, bool expectedResult) + { + // quick sanity check: run the parameters against the normal string functions to ensure our test data is valid + if (a != null) + { + Assert.Equal(expectedResult, a.EndsWith(b, comparisonType)); + } + + // Arrange + StringSegment sa = MakePaddedStringSegment(a); + + // Act + bool actualResult = sa.EndsWith(b, comparisonType); + + // Assert + Assert.Equal(expectedResult, actualResult); + } + + private static StringSegment MakePaddedStringSegment(string input) + { + return (input is null) ? new StringSegment() : new StringSegment("xx" + input + "zzz", 2, input.Length); + } } } diff --git a/src/libraries/Microsoft.Win32.Registry/ref/Microsoft.Win32.Registry.cs b/src/libraries/Microsoft.Win32.Registry/ref/Microsoft.Win32.Registry.cs index ea51db47d32ed..99acbd126fbcb 100644 --- a/src/libraries/Microsoft.Win32.Registry/ref/Microsoft.Win32.Registry.cs +++ b/src/libraries/Microsoft.Win32.Registry/ref/Microsoft.Win32.Registry.cs @@ -114,6 +114,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeRegistryHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public SafeRegistryHandle() : base (default(bool)) {} public SafeRegistryHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { } protected override bool ReleaseHandle() { throw null; } } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Globalization.Native/CMakeLists.txt index 834a86ec1cca0..080ac34fdf623 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Globalization.Native/CMakeLists.txt @@ -60,9 +60,12 @@ set(NATIVEGLOBALIZATION_SOURCES pal_normalization.c pal_timeZoneInfo.c pal_icushim.c - entrypoints.c ) +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) + set(NATIVEGLOBALIZATION_SOURCES ${NATIVEGLOBALIZATION_SOURCES} entrypoints.c) +endif() + if (MSVC) set_source_files_properties(${NATIVEGLOBALIZATION_SOURCES} PROPERTIES LANGUAGE CXX) endif() @@ -89,19 +92,12 @@ add_library(System.Globalization.Native-Static ${NATIVEGLOBALIZATION_SOURCES} ) -set_target_properties(System.Globalization.Native-Static PROPERTIES OUTPUT_NAME System.Globalization.Native CLEAN_DIRECT_OUTPUT 1) - -if (GEN_SHARED_LIB) - # this builds with libs to check for libs-level warnings - install (TARGETS System.Globalization.Native-Static DESTINATION .) -else() - # this one builds with coreclr and used by singlefilehost. - # it needs to build with coreclr in order to be linkable with it on Windows - # coreclr and static libraries must match because linker does not permit mixing modules - # with different C runtimes (i.e. Debug vs. Release) - install (TARGETS System.Globalization.Native-Static DESTINATION lib) +if(CLR_CMAKE_TARGET_UNIX) + set_target_properties(System.Globalization.Native-Static PROPERTIES OUTPUT_NAME System.Globalization.Native CLEAN_DIRECT_OUTPUT 1) endif() +install (TARGETS System.Globalization.Native-Static DESTINATION ${STATIC_LIB_DESTINATION}) + if(NOT CLR_CMAKE_TARGET_OSX AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID) if (GEN_SHARED_LIB) add_custom_command(TARGET System.Globalization.Native POST_BUILD diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_calendarData.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_calendarData.c index e7960716ea039..709b27328dfed 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_calendarData.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_calendarData.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. #include +#include #include #include #include "pal_locale_internal.h" @@ -126,8 +127,8 @@ int32_t GlobalizationNative_GetCalendars( { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); - UEnumeration* pEnum = ucal_getKeywordValuesForLocale("calendar", locale, TRUE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); + UEnumeration* pEnum = ucal_getKeywordValuesForLocale("calendar", locale, true, &err); int stringEnumeratorCount = uenum_count(pEnum, &err); int calendarsReturned = 0; for (int i = 0; i < stringEnumeratorCount && calendarsReturned < calendarsCapacity; i++) @@ -197,7 +198,7 @@ ResultCode GlobalizationNative_GetCalendarInfo( { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); if (U_FAILURE(err)) return UnknownError; @@ -209,7 +210,7 @@ ResultCode GlobalizationNative_GetCalendarInfo( case CalendarData_MonthDay: return GetMonthDayPattern(locale, result, resultCapacity); default: - assert(FALSE); + assert(false); return UnknownError; } } @@ -230,19 +231,19 @@ static int InvokeCallbackForDatePattern(const char* locale, UDateFormat* pFormat = udat_open(UDAT_NONE, style, locale, NULL, 0, NULL, 0, &err); if (U_FAILURE(err)) - return FALSE; + return false; UErrorCode ignore = U_ZERO_ERROR; - int32_t patternLen = udat_toPattern(pFormat, FALSE, NULL, 0, &ignore) + 1; + int32_t patternLen = udat_toPattern(pFormat, false, NULL, 0, &ignore) + 1; UChar* pattern = (UChar*)calloc((size_t)patternLen, sizeof(UChar)); if (pattern == NULL) { udat_close(pFormat); - return FALSE; + return false; } - udat_toPattern(pFormat, FALSE, pattern, patternLen, &err); + udat_toPattern(pFormat, false, pattern, patternLen, &err); udat_close(pFormat); if (U_SUCCESS(err)) @@ -270,7 +271,7 @@ static int InvokeCallbackForDateTimePattern(const char* locale, UDateTimePatternGenerator* pGenerator = udatpg_open(locale, &err); if (U_FAILURE(err)) - return FALSE; + return false; UErrorCode ignore = U_ZERO_ERROR; int32_t patternLen = udatpg_getBestPattern(pGenerator, patternSkeleton, -1, NULL, 0, &ignore) + 1; @@ -279,7 +280,7 @@ static int InvokeCallbackForDateTimePattern(const char* locale, if (bestPattern == NULL) { udatpg_close(pGenerator); - return FALSE; + return false; } udatpg_getBestPattern(pGenerator, patternSkeleton, -1, bestPattern, patternLen, &err); @@ -312,7 +313,7 @@ static int32_t EnumSymbols(const char* locale, UDateFormat* pFormat = udat_open(UDAT_DEFAULT, UDAT_DEFAULT, locale, NULL, 0, NULL, 0, &err); if (U_FAILURE(err)) - return FALSE; + return false; char localeWithCalendarName[ULOC_FULLNAME_CAPACITY]; STRING_COPY(localeWithCalendarName, sizeof(localeWithCalendarName), locale); @@ -324,7 +325,7 @@ static int32_t EnumSymbols(const char* locale, if (U_FAILURE(err)) { udat_close(pFormat); - return FALSE; + return false; } udat_setCalendar(pFormat, pCalendar); @@ -424,7 +425,7 @@ static int32_t EnumAbbrevEraNames(const char* locale, char* parentNamePtr = parentNameBuf; STRING_COPY(localeNamePtr, sizeof(localeNameBuf), locale); - while (TRUE) + while (true) { UErrorCode status = U_ZERO_ERROR; const char* name = GetCalendarName(calendarId); @@ -439,7 +440,7 @@ static int32_t EnumAbbrevEraNames(const char* locale, { EnumUResourceBundle(erasResBundle, callback, context); CloseResBundle(rootResBundle, calResBundle, targetCalResBundle, erasColResBundle, erasResBundle); - return TRUE; + return true; } // Couldn't find the data we need for this locale, we should fallback. @@ -492,10 +493,10 @@ int32_t GlobalizationNative_EnumCalendarInfo(EnumCalendarInfoCallback callback, { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); if (U_FAILURE(err)) - return FALSE; + return false; switch (dataType) { @@ -536,8 +537,8 @@ int32_t GlobalizationNative_EnumCalendarInfo(EnumCalendarInfoCallback callback, case CalendarData_AbbrevEraNames: return EnumAbbrevEraNames(locale, calendarId, callback, context); default: - assert(FALSE); - return FALSE; + assert(false); + return false; } } @@ -581,7 +582,7 @@ int32_t GlobalizationNative_GetJapaneseEraStartDate(int32_t era, UCalendar* pCal = ucal_open(NULL, 0, JAPANESE_LOCALE_AND_CALENDAR, UCAL_TRADITIONAL, &err); if (U_FAILURE(err)) - return FALSE; + return false; ucal_set(pCal, UCAL_ERA, era); ucal_set(pCal, UCAL_YEAR, 1); @@ -591,7 +592,7 @@ int32_t GlobalizationNative_GetJapaneseEraStartDate(int32_t era, if (U_FAILURE(err)) { ucal_close(pCal); - return FALSE; + return false; } // set the date to Jan 1 @@ -628,5 +629,5 @@ int32_t GlobalizationNative_GetJapaneseEraStartDate(int32_t era, } ucal_close(pCal); - return FALSE; + return false; } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_casing.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_casing.c index ceca03b53f567..aad75f64da7d9 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_casing.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_casing.c @@ -3,6 +3,7 @@ // #include +#include #include #include "pal_icushim_internal.h" @@ -34,7 +35,7 @@ void GlobalizationNative_ChangeCase( // compiler wasn't doing that optimization, and it results in an ~15-20% perf // improvement on longer strings.) - UBool isError = FALSE; + UBool isError = false; (void)isError; // only used for assert int32_t srcIdx = 0, dstIdx = 0; UChar32 srcCodepoint, dstCodepoint; @@ -46,7 +47,7 @@ void GlobalizationNative_ChangeCase( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = u_toupper(srcCodepoint); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } else @@ -56,7 +57,7 @@ void GlobalizationNative_ChangeCase( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = u_tolower(srcCodepoint); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } } @@ -74,7 +75,7 @@ void GlobalizationNative_ChangeCaseInvariant( { // See algorithmic comment in ChangeCase. - UBool isError = FALSE; + UBool isError = false; (void)isError; // only used for assert int32_t srcIdx = 0, dstIdx = 0; UChar32 srcCodepoint, dstCodepoint; @@ -89,7 +90,7 @@ void GlobalizationNative_ChangeCaseInvariant( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = ((srcCodepoint == (UChar32)0x0131) ? (UChar32)0x0131 : u_toupper(srcCodepoint)); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } else @@ -102,7 +103,7 @@ void GlobalizationNative_ChangeCaseInvariant( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = ((srcCodepoint == (UChar32)0x0130) ? (UChar32)0x0130 : u_tolower(srcCodepoint)); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } } @@ -119,7 +120,7 @@ void GlobalizationNative_ChangeCaseTurkish( { // See algorithmic comment in ChangeCase. - UBool isError = FALSE; + UBool isError = false; (void)isError; // only used for assert int32_t srcIdx = 0, dstIdx = 0; UChar32 srcCodepoint, dstCodepoint; @@ -133,7 +134,7 @@ void GlobalizationNative_ChangeCaseTurkish( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = ((srcCodepoint == (UChar32)0x0069) ? (UChar32)0x0130 : u_toupper(srcCodepoint)); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } else @@ -145,7 +146,7 @@ void GlobalizationNative_ChangeCaseTurkish( U16_NEXT(lpSrc, srcIdx, cwSrcLength, srcCodepoint); dstCodepoint = ((srcCodepoint == (UChar32)0x0049) ? (UChar32)0x0131 : u_tolower(srcCodepoint)); U16_APPEND(lpDst, dstIdx, cwDstLength, dstCodepoint, isError); - assert(isError == FALSE && srcIdx == dstIdx); + assert(isError == false && srcIdx == dstIdx); } } } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_collation.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_collation.c index 99edfd96bcf1f..7c02c4ede71a4 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_collation.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_collation.c @@ -368,7 +368,7 @@ static UCollator* CloneCollatorWithOptions(const UCollator* pCollator, int32_t o // Returns TRUE if all the collation elements in str are completely ignorable static int CanIgnoreAllCollationElements(const UCollator* pColl, const UChar* lpStr, int32_t length) { - int result = TRUE; + int result = true; UErrorCode err = U_ZERO_ERROR; UCollationElements* pCollElem = ucol_openElements(pColl, lpStr, length, &err); @@ -379,7 +379,7 @@ static int CanIgnoreAllCollationElements(const UCollator* pColl, const UChar* lp { if (curCollElem != UCOL_IGNORABLE) { - result = FALSE; + result = false; break; } } @@ -387,7 +387,7 @@ static int CanIgnoreAllCollationElements(const UCollator* pColl, const UChar* lp ucol_closeElements(pCollElem); } - return U_SUCCESS(err) ? result : FALSE; + return U_SUCCESS(err) ? result : false; } static void CreateSortHandle(SortHandle** ppSortHandle) @@ -496,7 +496,7 @@ static inline int32_t CreateNewSearchNode(SortHandle* pSortHandle, int32_t optio SearchIteratorNode* node = (SearchIteratorNode*) malloc(sizeof(SearchIteratorNode)); if (node == NULL) { - return FALSE; + return false; } node->searchIterator = USED_STRING_SEARCH; // Mark the new node search handle as borrowed. @@ -517,9 +517,9 @@ static inline int32_t CreateNewSearchNode(SortHandle* pSortHandle, int32_t optio pCurrent = pCurrent->next; - } while (TRUE); + } while (true); - return TRUE; + return true; } // Restore previously borrowed search handle to the linked list. @@ -531,12 +531,12 @@ static inline int32_t RestoreSearchHandle(SortHandle* pSortHandle, UStringSearch { if (pCurrent->searchIterator == USED_STRING_SEARCH && pal_atomic_cas_ptr((void* volatile*)&(pCurrent->searchIterator), pSearchIterator, USED_STRING_SEARCH)) { - return TRUE; + return true; } pCurrent = pCurrent->next; } - return FALSE; + return false; } // return -1 if couldn't borrow search handle from the SortHandle cache, otherwise, it return the slot number of the cache. @@ -559,7 +559,7 @@ static int32_t GetSearchIteratorUsingCollator( *pSearchIterator = usearch_openFromCollator(lpTarget, cwTargetLength, lpSource, cwSourceLength, pColl, NULL, &err); if (!U_SUCCESS(err)) { - assert(FALSE && "Couldn't open the search iterator."); + assert(false && "Couldn't open the search iterator."); return -1; } @@ -597,7 +597,7 @@ static int32_t GetSearchIteratorUsingCollator( *pSearchIterator = usearch_openFromCollator(lpTarget, cwTargetLength, lpSource, cwSourceLength, pColl, NULL, &err); if (!U_SUCCESS(err)) { - assert(FALSE && "Couldn't open a new search iterator."); + assert(false && "Couldn't open a new search iterator."); return -1; } @@ -645,7 +645,7 @@ static inline int32_t GetSearchIterator( const UCollator* pColl = GetCollatorFromSortHandle(pSortHandle, options, &err); if (!U_SUCCESS(err)) { - assert(FALSE && "Couldn't get the collator."); + assert(false && "Couldn't get the collator."); return -1; } @@ -672,7 +672,7 @@ int32_t GlobalizationNative_GetSortVersion(SortHandle* pSortHandle) } else { - assert(FALSE && "Unexpected ucol_getVersion to fail."); + assert(false && "Unexpected ucol_getVersion to fail."); } return result; } @@ -860,13 +860,13 @@ static int32_t inline SimpleAffix_Iterators(UCollationElements* pPatternIterator assert(strength >= UCOL_SECONDARY); UErrorCode errorCode = U_ZERO_ERROR; - int32_t movePattern = TRUE, moveSource = TRUE; + int32_t movePattern = true, moveSource = true; int32_t patternElement = UCOL_IGNORABLE, sourceElement = UCOL_IGNORABLE; int32_t capturedOffset = 0; int32_t collationElementMask = GetCollationElementMask(strength); - while (TRUE) + while (true) { if (movePattern) { @@ -880,7 +880,7 @@ static int32_t inline SimpleAffix_Iterators(UCollationElements* pPatternIterator } sourceElement = forwardSearch ? ucol_next(pSourceIterator, &errorCode) : ucol_previous(pSourceIterator, &errorCode); } - movePattern = TRUE; moveSource = TRUE; + movePattern = true; moveSource = true; if (patternElement == UCOL_NULLORDER) { @@ -894,7 +894,7 @@ static int32_t inline SimpleAffix_Iterators(UCollationElements* pPatternIterator } else if (forwardSearch && ((sourceElement & UCOL_PRIMARYORDERMASK) == 0) && (sourceElement & UCOL_SECONDARYORDERMASK) != 0) { - return FALSE; // the next character in source text is a combining character, an example: "o\u0308".StartsWith("o") + return false; // the next character in source text is a combining character, an example: "o\u0308".StartsWith("o") } else { @@ -903,15 +903,15 @@ static int32_t inline SimpleAffix_Iterators(UCollationElements* pPatternIterator } else if (patternElement == UCOL_IGNORABLE) { - moveSource = FALSE; + moveSource = false; } else if (sourceElement == UCOL_IGNORABLE) { - movePattern = FALSE; + movePattern = false; } else if ((patternElement & collationElementMask) != (sourceElement & collationElementMask)) { - return FALSE; + return false; } } @@ -920,12 +920,12 @@ static int32_t inline SimpleAffix_Iterators(UCollationElements* pPatternIterator { *pCapturedOffset = capturedOffset; } - return TRUE; + return true; } static int32_t SimpleAffix(const UCollator* pCollator, UErrorCode* pErrorCode, const UChar* pPattern, int32_t patternLength, const UChar* pText, int32_t textLength, int32_t forwardSearch, int32_t* pMatchedLength) { - int32_t result = FALSE; + int32_t result = false; UCollationElements* pPatternIterator = ucol_openElements(pCollator, pPattern, patternLength, pErrorCode); if (U_SUCCESS(*pErrorCode)) @@ -956,7 +956,7 @@ static int32_t SimpleAffix(const UCollator* pCollator, UErrorCode* pErrorCode, c static int32_t ComplexStartsWith(SortHandle* pSortHandle, const UChar* pPattern, int32_t patternLength, const UChar* pText, int32_t textLength, int32_t options, int32_t* pMatchedLength) { - int32_t result = FALSE; + int32_t result = false; UErrorCode err = U_ZERO_ERROR; const UCollator* pCollator = GetCollatorFromSortHandle(pSortHandle, options, &err); @@ -977,7 +977,7 @@ static int32_t ComplexStartsWith(SortHandle* pSortHandle, const UChar* pPattern, { if (idx == 0) { - result = TRUE; + result = true; } else { @@ -1017,15 +1017,15 @@ int32_t GlobalizationNative_StartsWith( const UCollator* pCollator = GetCollatorFromSortHandle(pSortHandle, options, &err); if (!U_SUCCESS(err)) { - return FALSE; + return false; } - return SimpleAffix(pCollator, &err, lpTarget, cwTargetLength, lpSource, cwSourceLength, TRUE, pMatchedLength); + return SimpleAffix(pCollator, &err, lpTarget, cwTargetLength, lpSource, cwSourceLength, true, pMatchedLength); } static int32_t ComplexEndsWith(SortHandle* pSortHandle, const UChar* pPattern, int32_t patternLength, const UChar* pText, int32_t textLength, int32_t options, int32_t* pMatchedLength) { - int32_t result = FALSE; + int32_t result = false; UErrorCode err = U_ZERO_ERROR; const UCollator* pCollator = GetCollatorFromSortHandle(pSortHandle, options, &err); @@ -1049,7 +1049,7 @@ static int32_t ComplexEndsWith(SortHandle* pSortHandle, const UChar* pPattern, i if (matchEnd == textLength) { - result = TRUE; + result = true; } else { @@ -1092,9 +1092,9 @@ int32_t GlobalizationNative_EndsWith( if (!U_SUCCESS(err)) { - return FALSE; + return false; } - return SimpleAffix(pCollator, &err, lpTarget, cwTargetLength, lpSource, cwSourceLength, FALSE, pMatchedLength); + return SimpleAffix(pCollator, &err, lpTarget, cwTargetLength, lpSource, cwSourceLength, false, pMatchedLength); } int32_t GlobalizationNative_GetSortKey( diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c index 43e1d4fb370bc..99a7899b84835 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_icushim.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // +#include #include #include "pal_icushim_internal.h" @@ -58,7 +59,7 @@ static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbo if (dlsym(libicuuc, symbolName) == NULL) { if (minorVer == -1) - return FALSE; + return false; // Now try the _majorVer_minorVer added sprintf(symbolVersion, "_%d_%d%s", majorVer, minorVer, suffix); @@ -66,20 +67,20 @@ static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbo if (dlsym(libicuuc, symbolName) == NULL) { if (subVer == -1) - return FALSE; + return false; // Finally, try the _majorVer_minorVer_subVer added sprintf(symbolVersion, "_%d_%d_%d%s", majorVer, minorVer, subVer, suffix); sprintf(symbolName, "u_strlen%s", symbolVersion); if (dlsym(libicuuc, symbolName) == NULL) { - return FALSE; + return false; } } } } - return TRUE; + return true; } #endif // TARGET_UNIX @@ -98,12 +99,12 @@ static int FindICULibs() libicuuc = LoadLibraryExW(L"icu.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); if (libicuuc == NULL) { - return FALSE; + return false; } // Windows has a single dll for icu. libicui18n = libicuuc; - return TRUE; + return true; } static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbolName, char* symbolVersion, char* suffix) @@ -119,7 +120,7 @@ static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbo if (GetProcAddress(lib, symbolName) == NULL) { if (minorVer == -1) - return FALSE; + return false; // Now try the _majorVer_minorVer added sprintf_s(symbolVersion, MaxICUVersionStringWithSuffixLength, "_%d_%d%s", majorVer, minorVer, suffix); @@ -127,19 +128,19 @@ static int FindSymbolVersion(int majorVer, int minorVer, int subVer, char* symbo if (GetProcAddress(lib, symbolName) == NULL) { if (subVer == -1) - return FALSE; + return false; // Finally, try the _majorVer_minorVer_subVer added sprintf_s(symbolVersion, MaxICUVersionStringWithSuffixLength, "_%d_%d_%d%s", majorVer, minorVer, subVer, suffix); sprintf_s(symbolName, SYMBOL_NAME_SIZE, "u_strlen%s", symbolVersion); if (GetProcAddress(lib, symbolName) == NULL) { - return FALSE; + return false; } } } } - return TRUE; + return true; } #elif defined(TARGET_OSX) @@ -155,13 +156,13 @@ static int FindICULibs() if (libicuuc == NULL) { - return FALSE; + return false; } // in OSX all ICU APIs exist in the same library libicucore.A.dylib libicui18n = libicuuc; - return TRUE; + return true; } #elif defined(TARGET_ANDROID) @@ -176,14 +177,14 @@ static int FindICULibs(char* symbolName, char* symbolVersion) if (libicui18n == NULL) { - return FALSE; + return false; } libicuuc = dlopen("libicuuc.so", RTLD_LAZY); if (libicuuc == NULL) { - return FALSE; + return false; } char symbolSuffix[SYMBOL_CUSTOM_SUFFIX_SIZE]=""; @@ -191,12 +192,12 @@ static int FindICULibs(char* symbolName, char* symbolVersion) { if (FindSymbolVersion(i, -1, -1, symbolName, symbolVersion, symbolSuffix)) { - return TRUE; + return true; } } fprintf(stderr, "Cannot determine ICU version."); - return FALSE; + return false; } #else // !TARGET_WINDOWS && !TARGET_OSX && !TARGET_ANDROID @@ -290,12 +291,12 @@ static int FindLibUsingOverride(const char* versionPrefix, char* symbolName, cha { if (OpenICULibraries(first, second, third, versionPrefix, symbolName, symbolVersion)) { - return TRUE; + return true; } } } - return FALSE; + return false; } // Search for library files with names including the major version. @@ -309,11 +310,11 @@ static int FindLibWithMajorVersion(const char* versionPrefix, char* symbolName, { if (OpenICULibraries(i, -1, -1, versionPrefix, symbolName, symbolVersion)) { - return TRUE; + return true; } } - return FALSE; + return false; } // Select the highest supported version of ICU present on the local machine @@ -326,12 +327,12 @@ static int FindLibWithMajorMinorVersion(const char* versionPrefix, char* symbolN { if (OpenICULibraries(i, j, -1, versionPrefix, symbolName, symbolVersion)) { - return TRUE; + return true; } } } - return FALSE; + return false; } // Select the highest supported version of ICU present on the local machine @@ -346,13 +347,13 @@ static int FindLibWithMajorMinorSubVersion(const char* versionPrefix, char* symb { if (OpenICULibraries(i, j, k, versionPrefix, symbolName, symbolVersion)) { - return TRUE; + return true; } } } } - return FALSE; + return false; } @@ -392,27 +393,27 @@ int32_t GlobalizationNative_LoadICU() if (!FindICULibs()) { - return FALSE; + return false; } #elif defined(TARGET_ANDROID) if (!FindICULibs(symbolName, symbolVersion)) { - return FALSE; + return false; } #else if (!FindICULibs(VERSION_PREFIX_NONE, symbolName, symbolVersion)) { if (!FindICULibs(VERSION_PREFIX_SUSE, symbolName, symbolVersion)) { - return FALSE; + return false; } } #endif // TARGET_WINDOWS || TARGET_OSX FOR_ALL_ICU_FUNCTIONS ValidateICUDataCanLoad(); - return TRUE; + return true; } void GlobalizationNative_InitICUFunctions(void* icuuc, void* icuin, const char* version, const char* suffix) diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c index 5024fb0da84e2..cd0e25ed8837f 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_locale.c @@ -3,6 +3,7 @@ // #include +#include #include #include #include @@ -15,7 +16,7 @@ int32_t UErrorCodeToBool(UErrorCode status) { if (U_SUCCESS(status)) { - return TRUE; + return true; } // assert errors that should never occur @@ -24,7 +25,7 @@ int32_t UErrorCodeToBool(UErrorCode status) // add possible SetLastError support here - return FALSE; + return false; } int32_t GetLocale(const UChar* localeName, @@ -197,7 +198,7 @@ int32_t GlobalizationNative_GetLocaleName(const UChar* localeName, UChar* value, UErrorCode status = U_ZERO_ERROR; char localeNameBuffer[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, localeNameBuffer, ULOC_FULLNAME_CAPACITY, TRUE, &status); + GetLocale(localeName, localeNameBuffer, ULOC_FULLNAME_CAPACITY, true, &status); u_charsToUChars_safe(localeNameBuffer, value, valueLength, &status); if (U_SUCCESS(status)) @@ -245,10 +246,10 @@ int32_t GlobalizationNative_IsPredefinedLocale(const UChar* localeName) { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); if (U_FAILURE(err)) - return FALSE; + return false; // ures_open returns err = U_ZERO_ERROR when ICU has data for localeName. // If it is fake locale, it will return err = U_USING_FALLBACK_WARNING || err = U_USING_DEFAULT_WARNING. diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_localeNumberData.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_localeNumberData.c index 3e313858920db..686fbf5631fd9 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_localeNumberData.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_localeNumberData.c @@ -3,6 +3,7 @@ // #include +#include #include #include @@ -56,10 +57,10 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) } int index = 0; - int minusAdded = FALSE; - int digitAdded = FALSE; - int currencyAdded = FALSE; - int spaceAdded = FALSE; + int minusAdded = false; + int digitAdded = false; + int currencyAdded = false; + int spaceAdded = false; for (int i = iStart; i <= iEnd; i++) { @@ -69,7 +70,7 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) case UCHAR_MINUS: case UCHAR_OPENPAREN: case UCHAR_CLOSEPAREN: - minusAdded = TRUE; + minusAdded = true; break; } } @@ -104,7 +105,7 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) case UCHAR_ZERO: if (!digitAdded) { - digitAdded = TRUE; + digitAdded = true; destPattern[index++] = 'n'; } break; @@ -112,7 +113,7 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) case UCHAR_CURRENCY: if (!currencyAdded) { - currencyAdded = TRUE; + currencyAdded = true; destPattern[index++] = 'C'; } break; @@ -121,7 +122,7 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) case UCHAR_NBSPACE: if (!spaceAdded) { - spaceAdded = TRUE; + spaceAdded = true; destPattern[index++] = ' '; } break; @@ -129,7 +130,7 @@ static char* NormalizeNumericPattern(const UChar* srcPattern, int isNegative) case UCHAR_MINUS: case UCHAR_OPENPAREN: case UCHAR_CLOSEPAREN: - minusAdded = TRUE; + minusAdded = true; destPattern[index++] = (char)ch; break; @@ -159,7 +160,7 @@ static int GetNumericPattern(const UNumberFormat* pNumberFormat, const int MAX_DOTNET_NUMERIC_PATTERN_LENGTH = 6; // example: "(C n)" plus terminator UErrorCode ignore = U_ZERO_ERROR; - int32_t icuPatternLength = unum_toPattern(pNumberFormat, FALSE, NULL, 0, &ignore) + 1; + int32_t icuPatternLength = unum_toPattern(pNumberFormat, false, NULL, 0, &ignore) + 1; UChar* icuPattern = (UChar*)calloc((size_t)icuPatternLength, sizeof(UChar)); if (icuPattern == NULL) @@ -169,7 +170,7 @@ static int GetNumericPattern(const UNumberFormat* pNumberFormat, UErrorCode err = U_ZERO_ERROR; - unum_toPattern(pNumberFormat, FALSE, icuPattern, icuPatternLength, &err); + unum_toPattern(pNumberFormat, false, icuPattern, icuPatternLength, &err); assert(U_SUCCESS(err)); @@ -197,7 +198,7 @@ static int GetNumericPattern(const UNumberFormat* pNumberFormat, } } - assert(FALSE); // should have found a valid pattern + assert(false); // should have found a valid pattern free(normalizedPattern); return INVALID_FORMAT; @@ -238,7 +239,7 @@ static int GetCurrencyNegativePattern(const char* locale) if (U_SUCCESS(status)) { - int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), TRUE); + int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true); if (value >= 0) { unum_close(pFormat); @@ -269,7 +270,7 @@ static int GetCurrencyPositivePattern(const char* locale) if (U_SUCCESS(status)) { - int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), FALSE); + int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), false); if (value >= 0) { unum_close(pFormat); @@ -300,7 +301,7 @@ static int GetNumberNegativePattern(const char* locale) if (U_SUCCESS(status)) { - int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), TRUE); + int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true); if (value >= 0) { unum_close(pFormat); @@ -332,7 +333,7 @@ static int GetPercentNegativePattern(const char* locale) if (U_SUCCESS(status)) { - int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), TRUE); + int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), true); if (value >= 0) { unum_close(pFormat); @@ -363,7 +364,7 @@ static int GetPercentPositivePattern(const char* locale) if (U_SUCCESS(status)) { - int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), FALSE); + int value = GetNumericPattern(pFormat, Patterns, ARRAY_LENGTH(Patterns), false); if (value >= 0) { unum_close(pFormat); @@ -407,11 +408,11 @@ int32_t GlobalizationNative_GetLocaleInfoInt( { UErrorCode status = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &status); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status); if (U_FAILURE(status)) { - return FALSE; + return false; } switch (localeNumberData) @@ -514,7 +515,7 @@ int32_t GlobalizationNative_GetLocaleInfoInt( break; default: status = U_UNSUPPORTED_ERROR; - assert(FALSE); + assert(false); break; } @@ -533,7 +534,7 @@ int32_t GlobalizationNative_GetLocaleInfoGroupingSizes( { UErrorCode status = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &status); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status); if (U_FAILURE(status)) { diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_localeStringData.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_localeStringData.c index 669d59f88e99c..6e10e3543deb5 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_localeStringData.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_localeStringData.c @@ -3,6 +3,7 @@ // #include +#include #include #include @@ -210,7 +211,7 @@ int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName, { UErrorCode status = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &status); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &status); if (U_FAILURE(status)) { @@ -266,10 +267,10 @@ int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName, status = GetLocaleInfoDecimalFormatSymbol(locale, UNUM_INTL_CURRENCY_SYMBOL, value, valueLength); break; case LocaleString_CurrencyEnglishName: - status = GetLocaleCurrencyName(locale, FALSE, value, valueLength); + status = GetLocaleCurrencyName(locale, false, value, valueLength); break; case LocaleString_CurrencyNativeName: - status = GetLocaleCurrencyName(locale, TRUE, value, valueLength); + status = GetLocaleCurrencyName(locale, true, value, valueLength); break; case LocaleString_MonetaryDecimalSeparator: status = GetLocaleInfoDecimalFormatSymbol(locale, UNUM_MONETARY_SEPARATOR_SYMBOL, value, valueLength); @@ -279,10 +280,10 @@ int32_t GlobalizationNative_GetLocaleInfoString(const UChar* localeName, GetLocaleInfoDecimalFormatSymbol(locale, UNUM_MONETARY_GROUPING_SEPARATOR_SYMBOL, value, valueLength); break; case LocaleString_AMDesignator: - status = GetLocaleInfoAmPm(locale, TRUE, value, valueLength); + status = GetLocaleInfoAmPm(locale, true, value, valueLength); break; case LocaleString_PMDesignator: - status = GetLocaleInfoAmPm(locale, FALSE, value, valueLength); + status = GetLocaleInfoAmPm(locale, false, value, valueLength); break; case LocaleString_PositiveSign: status = GetLocaleInfoDecimalFormatSymbol(locale, UNUM_PLUS_SIGN_SYMBOL, value, valueLength); @@ -350,10 +351,10 @@ int32_t GlobalizationNative_GetLocaleTimeFormat(const UChar* localeName, { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); UDateFormatStyle style = (shortFormat != 0) ? UDAT_SHORT : UDAT_MEDIUM; UDateFormat* pFormat = udat_open(style, UDAT_NONE, locale, NULL, 0, NULL, 0, &err); - udat_toPattern(pFormat, FALSE, value, valueLength, &err); + udat_toPattern(pFormat, false, value, valueLength, &err); udat_close(pFormat); return UErrorCodeToBool(err); } diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_normalization.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_normalization.c index bd453f9ff1c9f..9faca845b7d89 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_normalization.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_normalization.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // +#include #include #include "pal_icushim_internal.h" @@ -47,7 +48,7 @@ int32_t GlobalizationNative_IsNormalized( if (U_SUCCESS(err)) { - return isNormalized == TRUE ? 1 : 0; + return isNormalized ? 1 : 0; } else { diff --git a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c index 802aa8515e208..ce865cdf0c1ae 100644 --- a/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c +++ b/src/libraries/Native/Unix/System.Globalization.Native/pal_timeZoneInfo.c @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // +#include #include #include "pal_errors_internal.h" @@ -19,7 +20,7 @@ ResultCode GlobalizationNative_GetTimeZoneDisplayName(const UChar* localeName, { UErrorCode err = U_ZERO_ERROR; char locale[ULOC_FULLNAME_CAPACITY]; - GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, FALSE, &err); + GetLocale(localeName, locale, ULOC_FULLNAME_CAPACITY, false, &err); int32_t timeZoneIdLength = -1; // timeZoneId is NULL-terminated UCalendar* calendar = ucal_open(timeZoneId, timeZoneIdLength, locale, UCAL_DEFAULT, &err); diff --git a/src/libraries/Native/Unix/System.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Native/CMakeLists.txt index 68f4df1f7ea17..641ac75735de9 100644 --- a/src/libraries/Native/Unix/System.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Native/CMakeLists.txt @@ -60,7 +60,7 @@ if (GEN_SHARED_LIB) ${NATIVE_LIBS_EXTRA} ) - if (NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID) + if (NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) add_custom_command(TARGET System.Native POST_BUILD COMMENT "Verifying System.Native entry points against entrypoints.c " COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../verify-entrypoints.sh @@ -74,10 +74,13 @@ if (GEN_SHARED_LIB) install_with_stripped_symbols (System.Native PROGRAMS .) endif () +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) + set(NATIVE_SOURCES ${NATIVE_SOURCES} entrypoints.c) +endif() + add_library(System.Native-Static STATIC ${NATIVE_SOURCES} - entrypoints.c ) set_target_properties(System.Native-Static PROPERTIES OUTPUT_NAME System.Native CLEAN_DIRECT_OUTPUT 1) diff --git a/src/libraries/Native/Unix/System.Native/pal_networking.c b/src/libraries/Native/Unix/System.Native/pal_networking.c index 5e0f37635d326..67d41f9a48d76 100644 --- a/src/libraries/Native/Unix/System.Native/pal_networking.c +++ b/src/libraries/Native/Unix/System.Native/pal_networking.c @@ -601,12 +601,6 @@ int32_t SystemNative_GetHostEntryForNameAsync(const uint8_t* address, int32_t ad return GetAddrInfoErrorFlags_EAI_BADARG; } - sa_family_t platformFamily; - if (!TryConvertAddressFamilyPalToPlatform(addressFamily, &platformFamily)) - { - return GetAddrInfoErrorFlags_EAI_FAMILY; - } - struct GetAddrInfoAsyncState* state = malloc(sizeof(*state) + addrlen + 1); if (state == NULL) @@ -622,24 +616,22 @@ int32_t SystemNative_GetHostEntryForNameAsync(const uint8_t* address, int32_t ad memcpy(state->address, address, addrlen + 1); - *state = (struct GetAddrInfoAsyncState) { - .gai_request = { - .ar_name = state->address, - .ar_service = NULL, - .ar_request = &state->hint, - .ar_result = NULL - }, - .gai_requests = &state->gai_request, - .sigevent = { - .sigev_notify = SIGEV_THREAD, - .sigev_value = { - .sival_ptr = state - }, - .sigev_notify_function = GetHostEntryForNameAsyncComplete + state->gai_request = (struct gaicb) { + .ar_name = state->address, + .ar_service = NULL, + .ar_request = &state->hint, + .ar_result = NULL + }; + state->gai_requests = &state->gai_request; + state->sigevent = (struct sigevent) { + .sigev_notify = SIGEV_THREAD, + .sigev_value = { + .sival_ptr = state }, - .entry = entry, - .callback = callback + .sigev_notify_function = GetHostEntryForNameAsyncComplete }; + state->entry = entry; + state->callback = callback; atomic_thread_fence(memory_order_release); diff --git a/src/libraries/Native/Unix/System.Native/pal_sysctl.c b/src/libraries/Native/Unix/System.Native/pal_sysctl.c index 3316923f45eb8..87cada4c6cc56 100644 --- a/src/libraries/Native/Unix/System.Native/pal_sysctl.c +++ b/src/libraries/Native/Unix/System.Native/pal_sysctl.c @@ -23,7 +23,7 @@ int32_t SystemNative_Sysctl(int* name, unsigned int namelen, void* value, size_t void* newp = NULL; size_t newlen = 0; -#if defined(__linux__) || defined(TARGET_WASM) +#if defined(TARGET_WASM) return sysctl(name, (int)(namelen), value, len, newp, newlen); #else return sysctl(name, namelen, value, len, newp, newlen); diff --git a/src/libraries/Native/Unix/System.Net.Security.Native/CMakeLists.txt b/src/libraries/Native/Unix/System.Net.Security.Native/CMakeLists.txt index e5cdf212dab6a..cf1739205bfa9 100644 --- a/src/libraries/Native/Unix/System.Net.Security.Native/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Net.Security.Native/CMakeLists.txt @@ -19,10 +19,13 @@ if (GEN_SHARED_LIB) ) endif() +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) + set(NATIVEGSS_SOURCES ${NATIVEGSS_SOURCES} entrypoints.c) +endif() + add_library(System.Net.Security.Native-Static STATIC ${NATIVEGSS_SOURCES} - entrypoints.c ) set_target_properties(System.Net.Security.Native-Static PROPERTIES OUTPUT_NAME System.Net.Security.Native CLEAN_DIRECT_OUTPUT 1) @@ -32,7 +35,7 @@ if (GEN_SHARED_LIB) ${NATIVE_LIBS_EXTRA} ) - if (NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID) + if (NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) add_custom_command(TARGET System.Net.Security.Native POST_BUILD COMMENT "Verifying System.Net.Security.Native entry points against entrypoints.c " COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../verify-entrypoints.sh diff --git a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt index a133a938cfe23..ae4d764cbbba9 100644 --- a/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt +++ b/src/libraries/Native/Unix/System.Security.Cryptography.Native.Apple/CMakeLists.txt @@ -39,10 +39,13 @@ if (GEN_SHARED_LIB) ) endif() +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) + set(NATIVECRYPTO_SOURCES ${NATIVECRYPTO_SOURCES} entrypoints.c) +endif() + add_library(System.Security.Cryptography.Native.Apple-Static STATIC ${NATIVECRYPTO_SOURCES} - entrypoints.c ) set_target_properties(System.Security.Cryptography.Native.Apple-Static PROPERTIES OUTPUT_NAME System.Security.Cryptography.Native.Apple CLEAN_DIRECT_OUTPUT 1) diff --git a/src/libraries/Native/Unix/configure.cmake b/src/libraries/Native/Unix/configure.cmake index 8d4efdd1ad357..2d82decfde624 100644 --- a/src/libraries/Native/Unix/configure.cmake +++ b/src/libraries/Native/Unix/configure.cmake @@ -806,9 +806,14 @@ check_type_size( BUILTIN_TYPES_ONLY) set(CMAKE_EXTRA_INCLUDE_FILES) # reset CMAKE_EXTRA_INCLUDE_FILES -check_include_files( - "sys/types.h;sys/sysctl.h" - HAVE_SYS_SYSCTL_H) +if (CLR_CMAKE_TARGET_LINUX) + # sysctl is deprecated on Linux + set(HAVE_SYS_SYSCTL_H 0) +else () + check_include_files( + "sys/types.h;sys/sysctl.h" + HAVE_SYS_SYSCTL_H) +endif() check_include_files( "sys/ioctl.h" diff --git a/src/libraries/Native/Windows/System.IO.Compression.Native/CMakeLists.txt b/src/libraries/Native/Windows/System.IO.Compression.Native/CMakeLists.txt index b997f956af5a2..f35ddcfce15ac 100644 --- a/src/libraries/Native/Windows/System.IO.Compression.Native/CMakeLists.txt +++ b/src/libraries/Native/Windows/System.IO.Compression.Native/CMakeLists.txt @@ -74,7 +74,6 @@ set (NATIVECOMPRESSION_SOURCES ../../AnyOS/brotli/enc/metablock.c ../../AnyOS/brotli/enc/static_dict.c ../../AnyOS/brotli/enc/utf8_util.c - ../../AnyOS/System.IO.Compression.Native/entrypoints.c ) #Include Brotli include files @@ -90,24 +89,26 @@ if (GEN_SHARED_LIB) ) endif() -add_library(System.IO.Compression.Native-static +if (NOT GEN_SHARED_LIB AND NOT CLR_CMAKE_TARGET_IOS AND NOT CLR_CMAKE_TARGET_TVOS AND NOT CLR_CMAKE_TARGET_ANDROID AND NOT CLR_CMAKE_TARGET_BROWSER) + set(NATIVECOMPRESSION_SOURCES ${NATIVECOMPRESSION_SOURCES} ../../AnyOS/System.IO.Compression.Native/entrypoints.c) +endif() + +add_library(System.IO.Compression.Native-Static STATIC ${NATIVECOMPRESSION_SOURCES} ) -SET_TARGET_PROPERTIES(System.IO.Compression.Native-static PROPERTIES OUTPUT_NAME System.IO.Compression.Native) - # Allow specification of arguments that should be passed to the linker if (GEN_SHARED_LIB) SET_TARGET_PROPERTIES(System.IO.Compression.Native PROPERTIES LINK_OPTIONS "${__LinkArgs};${__SharedLinkArgs}") endif() -SET_TARGET_PROPERTIES(System.IO.Compression.Native-static PROPERTIES STATIC_LIBRARY_OPTIONS "${__LinkArgs}") +SET_TARGET_PROPERTIES(System.IO.Compression.Native-Static PROPERTIES STATIC_LIBRARY_OPTIONS "${__LinkArgs}") # Allow specification of libraries that should be linked against if (GEN_SHARED_LIB) target_link_libraries(System.IO.Compression.Native ${__LinkLibraries}) endif() -target_link_libraries(System.IO.Compression.Native-static ${__LinkLibraries}) +target_link_libraries(System.IO.Compression.Native-Static ${__LinkLibraries}) if (GEN_SHARED_LIB) GENERATE_EXPORT_HEADER( System.IO.Compression.Native @@ -121,5 +122,4 @@ if (GEN_SHARED_LIB) install (FILES $ DESTINATION .) endif() -install (TARGETS System.IO.Compression.Native-static DESTINATION ${STATIC_LIB_DESTINATION}) - +install (TARGETS System.IO.Compression.Native-Static DESTINATION ${STATIC_LIB_DESTINATION}) diff --git a/src/libraries/Native/Windows/System.IO.Compression.Native/zlib.md b/src/libraries/Native/Windows/System.IO.Compression.Native/zlib.md new file mode 100644 index 0000000000000..71f9638254ac0 --- /dev/null +++ b/src/libraries/Native/Windows/System.IO.Compression.Native/zlib.md @@ -0,0 +1,6 @@ + +.\zlib (for Arm/Arm64) +is from https://github.com/madler/zlib/releases/tag/v1.2.11 + +.\zlib-intel (for x64/x86) +is from https://github.com/jtkukunas/zlib/tree/v1.2.11.1_jtkv6.3 diff --git a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs index 5c59e53f3cbcc..cb18979ec407d 100644 --- a/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/BlockingCollectionTests.cs @@ -741,7 +741,7 @@ public static void TestAddAnyTakeAny_Longrunning(int numOfAdds, int numOfTakes, /// are consumed by consumers with no element lost nor consumed more than once. /// Total number of producer and consumer threads. /// Number of elements to Add/Take per thread. - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(4, 2048, 2, 64)] [OuterLoop] private static void TestConcurrentAddAnyTakeAny(int numOfThreads, int numOfElementsPerThread, int numOfCollections, int boundOfCollections) diff --git a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs index 393b97759ca07..e466a37f920d7 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ConcurrentDictionary/ConcurrentDictionaryTests.cs @@ -1032,7 +1032,7 @@ public static void TestTryUpdate() } [OuterLoop("Runs for several seconds")] - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public void ConcurrentWriteRead_NoTornValues() { var cd = new ConcurrentDictionary>(); diff --git a/src/libraries/System.Collections.Concurrent/tests/ProducerConsumerCollectionTests.cs b/src/libraries/System.Collections.Concurrent/tests/ProducerConsumerCollectionTests.cs index ef699764e4214..f94c4a758fd8f 100644 --- a/src/libraries/System.Collections.Concurrent/tests/ProducerConsumerCollectionTests.cs +++ b/src/libraries/System.Collections.Concurrent/tests/ProducerConsumerCollectionTests.cs @@ -735,7 +735,7 @@ public void ManyConcurrentAddsTakes_EnsureTrackedCountsMatchResultingCollection( } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public void ManyConcurrentAddsTakes_CollectionRemainsConsistent() { diff --git a/src/libraries/System.Collections.NonGeneric/tests/HashtableTests.cs b/src/libraries/System.Collections.NonGeneric/tests/HashtableTests.cs index aea1086d0d58e..e50fd1a3c3858 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/HashtableTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/HashtableTests.cs @@ -1007,7 +1007,7 @@ public class Hashtable_ItemThreadSafetyTests private const int MAX_TEST_TIME_MS = 10000; // 10 seconds - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public void GetItem_ThreadSafety() { @@ -1100,7 +1100,7 @@ public class Hashtable_SynchronizedTests private Hashtable _hash2; private int _iNumberOfElements = 20; - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public void SynchronizedThreadSafety() { diff --git a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs index 8125d6a014a88..aa7c8f9ec1b17 100644 --- a/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs +++ b/src/libraries/System.Collections.NonGeneric/tests/SortedListTests.cs @@ -1537,7 +1537,7 @@ public class SortedList_SyncRootTests private SortedList _sortListGrandDaughter; private const int NumberOfElements = 100; - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public void GetSyncRootBasic() { diff --git a/src/libraries/System.ComponentModel.TypeConverter/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.ComponentModel.TypeConverter/src/ILLink/ILLink.Suppressions.xml index 63a84c9cd0d0f..d0db9830e3e59 100644 --- a/src/libraries/System.ComponentModel.TypeConverter/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.ComponentModel.TypeConverter/src/ILLink/ILLink.Suppressions.xml @@ -289,5 +289,17 @@ member M:System.ComponentModel.BindingList`1.get_ItemTypeHasDefaultConstructor + + ILLink + IL2026 + member + M:System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Deserialize(System.IO.Stream,System.String,System.ComponentModel.Design.RuntimeLicenseContext) + + + ILLink + IL2026 + member + M:System.ComponentModel.Design.DesigntimeLicenseContextSerializer.Serialize(System.IO.Stream,System.String,System.ComponentModel.Design.DesigntimeLicenseContext) + diff --git a/src/libraries/System.Console/tests/WindowAndCursorProps.cs b/src/libraries/System.Console/tests/WindowAndCursorProps.cs index 07309799a7549..1da4ce47340ab 100644 --- a/src/libraries/System.Console/tests/WindowAndCursorProps.cs +++ b/src/libraries/System.Console/tests/WindowAndCursorProps.cs @@ -276,7 +276,7 @@ public static void Title_SetNull_ThrowsArgumentNullException() AssertExtensions.Throws("value", () => Console.Title = null); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // makes noise, not very inner-loop friendly public static void Beep_Invoke_Success() { diff --git a/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml index 7b29974c298e8..d5d68da0e626e 100644 --- a/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Data.Common/src/ILLink/ILLink.Suppressions.xml @@ -133,5 +133,17 @@ member M:System.Data.DataColumn.get_DefaultValue + + ILLink + IL2026 + member + M:System.Data.DataSet.DeserializeDataSetSchema(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext,System.Data.SerializationFormat,System.Data.SchemaSerializationMode) + + + ILLink + IL2026 + member + M:System.Data.DataSet.SerializeDataSet(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext,System.Data.SerializationFormat) + - \ No newline at end of file + diff --git a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcEnvironmentHandle.cs b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcEnvironmentHandle.cs index 2b2177ba5e143..f816b2aa77717 100644 --- a/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcEnvironmentHandle.cs +++ b/src/libraries/System.Data.Odbc/src/System/Data/Odbc/OdbcEnvironmentHandle.cs @@ -8,7 +8,7 @@ namespace System.Data.Odbc { internal sealed class OdbcEnvironmentHandle : OdbcHandle { - internal OdbcEnvironmentHandle() : base(ODBC32.SQL_HANDLE.ENV, null) + public OdbcEnvironmentHandle() : base(ODBC32.SQL_HANDLE.ENV, null) { ODBC32.RetCode retcode; diff --git a/src/libraries/System.Data.OleDb/src/DbPropSet.cs b/src/libraries/System.Data.OleDb/src/DbPropSet.cs index 30935b1c6f80d..756c4612c3f1f 100644 --- a/src/libraries/System.Data.OleDb/src/DbPropSet.cs +++ b/src/libraries/System.Data.OleDb/src/DbPropSet.cs @@ -16,7 +16,7 @@ internal sealed class DBPropSet : SafeHandle // stores the exception with last error.HRESULT from IDBProperties.GetProperties private Exception? lastErrorFromProvider; - private DBPropSet() : base(IntPtr.Zero, true) + public DBPropSet() : base(IntPtr.Zero, true) { propertySetCount = 0; } diff --git a/src/libraries/System.Data.OleDb/src/SafeHandles.cs b/src/libraries/System.Data.OleDb/src/SafeHandles.cs index 185712be565c7..879d572447719 100644 --- a/src/libraries/System.Data.OleDb/src/SafeHandles.cs +++ b/src/libraries/System.Data.OleDb/src/SafeHandles.cs @@ -15,7 +15,7 @@ internal sealed class DualCoTaskMem : SafeHandle { private IntPtr handle2; // this must be protected so derived classes can use out params. - private DualCoTaskMem() : base(IntPtr.Zero, true) + public DualCoTaskMem() : base(IntPtr.Zero, true) { this.handle2 = IntPtr.Zero; } diff --git a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/WrappedIUnknown.cs b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/WrappedIUnknown.cs index a45f38e06111a..9b3205849d249 100644 --- a/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/WrappedIUnknown.cs +++ b/src/libraries/System.Data.OleDb/src/System/Data/ProviderBase/WrappedIUnknown.cs @@ -14,7 +14,7 @@ namespace System.Data.ProviderBase internal class WrappedIUnknown : SafeHandle { - internal WrappedIUnknown() : base(IntPtr.Zero, true) + public WrappedIUnknown() : base(IntPtr.Zero, true) { } 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 7b4aa10de2bf5..7ec1423a05a41 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/ref/System.Diagnostics.DiagnosticSourceActivity.cs @@ -135,9 +135,10 @@ public sealed class ActivitySource : IDisposable public string Name { get { throw null; } } public string? Version { get { throw null; } } public bool HasListeners() { throw null; } - public System.Diagnostics.Activity? StartActivity(string name, System.Diagnostics.ActivityKind kind = ActivityKind.Internal) { throw null; } + public System.Diagnostics.Activity? StartActivity([System.Runtime.CompilerServices.CallerMemberName] string name = "", System.Diagnostics.ActivityKind kind = ActivityKind.Internal) { throw null; } public System.Diagnostics.Activity? StartActivity(string name, System.Diagnostics.ActivityKind kind, System.Diagnostics.ActivityContext parentContext, System.Collections.Generic.IEnumerable>? tags = null, System.Collections.Generic.IEnumerable? links = null, System.DateTimeOffset startTime = default) { throw null; } public System.Diagnostics.Activity? StartActivity(string name, System.Diagnostics.ActivityKind kind, string parentId, System.Collections.Generic.IEnumerable>? tags = null, System.Collections.Generic.IEnumerable? links = null, System.DateTimeOffset startTime = default) { throw null; } + public System.Diagnostics.Activity? StartActivity(System.Diagnostics.ActivityKind kind, System.Diagnostics.ActivityContext parentContext = default, System.Collections.Generic.IEnumerable>? tags = null, System.Collections.Generic.IEnumerable? links = null, DateTimeOffset startTime = default, [System.Runtime.CompilerServices.CallerMemberName] string name = "") { throw null; } public static void AddActivityListener(System.Diagnostics.ActivityListener listener) { throw null; } public void Dispose() { throw null; } } diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj index f3a2c373d1872..fa0c354fdbcbc 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System.Diagnostics.DiagnosticSource.csproj @@ -32,8 +32,7 @@ - + @@ -45,6 +44,7 @@ + diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs index e867f367963c3..476af07d0c162 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs @@ -1763,15 +1763,20 @@ public void CopyTo(Span destination) } /// - /// Sets the bytes in 'outBytes' to be random values. outBytes.Length must be less than or equal to 16 + /// Sets the bytes in 'outBytes' to be random values. outBytes.Length must be either 8 or 16 bytes. /// /// internal static unsafe void SetToRandomBytes(Span outBytes) { - Debug.Assert(outBytes.Length <= sizeof(Guid)); // Guid is 16 bytes, and so is TraceId - Guid guid = Guid.NewGuid(); - ReadOnlySpan guidBytes = new ReadOnlySpan(&guid, sizeof(Guid)); - guidBytes.Slice(0, outBytes.Length).CopyTo(outBytes); + Debug.Assert(outBytes.Length == 16 || outBytes.Length == 8); + RandomNumberGenerator r = RandomNumberGenerator.Current; + + Unsafe.WriteUnaligned(ref outBytes[0], r.Next()); + + if (outBytes.Length == 16) + { + Unsafe.WriteUnaligned(ref outBytes[8], r.Next()); + } } /// diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs index 781e9ddca63d6..712fd58abf6d0 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/ActivitySource.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace System.Diagnostics { @@ -76,7 +77,7 @@ public bool HasListeners() /// The operation name of the Activity /// The /// The created object or null if there is no any event listener. - public Activity? StartActivity(string name, ActivityKind kind = ActivityKind.Internal) + public Activity? StartActivity([CallerMemberName] string name = "", ActivityKind kind = ActivityKind.Internal) => StartActivity(name, kind, default, null, null, null, default); /// @@ -105,6 +106,19 @@ public bool HasListeners() public Activity? StartActivity(string name, ActivityKind kind, string parentId, IEnumerable>? tags = null, IEnumerable? links = null, DateTimeOffset startTime = default) => StartActivity(name, kind, default, parentId, tags, links, startTime); + /// + /// Creates a new object if there is any listener to the Activity events, returns null otherwise. + /// + /// The + /// The parent object to initialize the created Activity object with. + /// The optional tags list to initialize the created Activity object with. + /// The optional list to initialize the created Activity object with. + /// The optional start timestamp to set on the created Activity object. + /// The operation name of the Activity. + /// The created object or null if there is no any listener. + public Activity? StartActivity(ActivityKind kind, ActivityContext parentContext = default, IEnumerable>? tags = null, IEnumerable? links = null, DateTimeOffset startTime = default, [CallerMemberName] string name = "") + => StartActivity(name, kind, parentContext, null, tags, links, startTime); + private Activity? StartActivity(string name, ActivityKind kind, ActivityContext context, string? parentId, IEnumerable>? tags, IEnumerable? links, DateTimeOffset startTime) { // _listeners can get assigned to null in Dispose. diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/RandomNumberGenerator.cs b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/RandomNumberGenerator.cs new file mode 100755 index 0000000000000..dd151339b5cf4 --- /dev/null +++ b/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/RandomNumberGenerator.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics +{ + /// + /// RandomNumberGenerator implementation is the 64-bit random number generator based on the Xoshiro256StarStar algorithm (known as shift-register generators). + /// + internal class RandomNumberGenerator + { + [ThreadStatic] private static RandomNumberGenerator? t_random; + + private ulong _s0, _s1, _s2, _s3; + + public static RandomNumberGenerator Current + { + get + { + if (t_random == null) + { + t_random = new RandomNumberGenerator(); + } + return t_random; + } + } + +#if ALLOW_PARTIALLY_TRUSTED_CALLERS + [System.Security.SecuritySafeCriticalAttribute] +#endif + public unsafe RandomNumberGenerator() + { + do + { + Guid g1 = Guid.NewGuid(); + Guid g2 = Guid.NewGuid(); + ulong* g1p = (ulong*)&g1; + ulong* g2p = (ulong*)&g2; + _s0 = *g1p; + _s1 = *(g1p + 1); + _s2 = *g2p; + _s3 = *(g2p + 1); + + // Guid uses the 4 most significant bits of the first long as the version which would be fixed and not randomized. + // and uses 2 other bits in the second long for variants which would be fixed and not randomized too. + // let's overwrite the fixed bits in each long part by the other long. + _s0 = (_s0 & 0x0FFFFFFFFFFFFFFF) | (_s1 & 0xF000000000000000); + _s2 = (_s2 & 0x0FFFFFFFFFFFFFFF) | (_s3 & 0xF000000000000000); + _s1 = (_s1 & 0xFFFFFFFFFFFFFF3F) | (_s0 & 0x00000000000000C0); + _s3 = (_s3 & 0xFFFFFFFFFFFFFF3F) | (_s2 & 0x00000000000000C0); + } + while ((_s0 | _s1 | _s2 | _s3) == 0); + } + + private ulong Rol64(ulong x, int k) => (x << k) | (x >> (64 - k)); + + public long Next() + { + ulong result = Rol64(_s1 * 5, 7) * 9; + ulong t = _s1 << 17; + + _s2 ^= _s0; + _s3 ^= _s1; + _s1 ^= _s2; + _s0 ^= _s3; + + _s2 ^= t; + _s3 = Rol64(_s3, 45); + + return (long)result; + } + } +} diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs index dff381149e76f..846b2147cb89f 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/ActivitySourceTests.cs @@ -125,6 +125,55 @@ public void TestActivityWithListenerActivityCreateAndAllDataRequested() }).Dispose(); } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] + public void TestActivityTriggeringCallerMemberNameAttribute() + { + RemoteExecutor.Invoke(() => { + using (ActivitySource aSource = new ActivitySource("SourceActivityTriggeringCallerMemberNameAttribute")) + { + using ActivityListener listener = new ActivityListener(); + listener.ShouldListenTo = (activitySource) => object.ReferenceEquals(aSource, activitySource); + listener.Sample = (ref ActivityCreationOptions activityOptions) => ActivitySamplingResult.AllData; + + ActivitySource.AddActivityListener(listener); + + string methodName = MethodBase.GetCurrentMethod().Name; + + using (Activity activity = aSource.StartActivity()) // passing default name should trigger CallerMemberName attribute. + { + Assert.NotNull(activity); + Assert.True(methodName.IndexOf(activity.OperationName, StringComparison.Ordinal) >= 0); + + using (Activity activity1 = aSource.StartActivity(ActivityKind.Client)) // passing default name should trigger CallerMemberName attribute. + { + Assert.NotNull(activity1); + Assert.True(methodName.IndexOf(activity1.OperationName, StringComparison.Ordinal) >= 0); + Assert.Equal(ActivityKind.Client, activity1.Kind); + } + + ActivityContext parentContext = new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None); + List> tags = new List>() { new KeyValuePair("Key", "Value") }; + List links = new List() { new ActivityLink(new ActivityContext(ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, "key-value")) }; + DateTimeOffset startTime = DateTimeOffset.UtcNow; + + using (Activity activity2 = aSource.StartActivity(ActivityKind.Server, parentContext, tags, links, startTime)) + { + Assert.NotNull(activity2); + Assert.True(methodName.IndexOf(activity2.OperationName, StringComparison.Ordinal) >= 0); + Assert.Equal(ActivityKind.Server, activity2.Kind); + Assert.Equal(tags, activity2.TagObjects); + Assert.Equal(links, activity2.Links); + Assert.Equal(startTime, activity2.StartTimeUtc); + Assert.Equal(parentContext.TraceId, activity2.TraceId); + Assert.Equal(parentContext.SpanId, activity2.ParentSpanId); + Assert.Equal(parentContext.TraceFlags, activity2.ActivityTraceFlags); + Assert.Equal(parentContext.TraceState, activity2.TraceStateString); + } + } + } + }).Dispose(); + } + [ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))] public void TestActivitySourceAttachedObject() { diff --git a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs index d09d45e484148..16267316c0320 100644 --- a/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs +++ b/src/libraries/System.Diagnostics.DiagnosticSource/tests/DiagnosticSourceTests.cs @@ -500,7 +500,7 @@ public void AllListenersCheckCatchupList() /// Stresses the AllListeners by having many threads be adding and removing. /// [OuterLoop] - [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/28772")] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArm64Process), nameof(PlatformDetection.IsThreadingSupported))] // [ActiveIssue("https://github.com/dotnet/runtime/issues/28772")] [InlineData(100, 100)] // run multiple times to stress it further [InlineData(100, 101)] [InlineData(100, 102)] diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemSafeHandle.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemSafeHandle.cs index 6181bc8d10441..b20e9b8127e3f 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemSafeHandle.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemSafeHandle.cs @@ -10,7 +10,7 @@ namespace System.Diagnostics.Eventing.Reader /// internal sealed class CoTaskMemSafeHandle : SafeHandle { - internal CoTaskMemSafeHandle() + public CoTaskMemSafeHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemUnicodeSafeHandle.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemUnicodeSafeHandle.cs index 658a961c7b91c..ce1037f515d81 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemUnicodeSafeHandle.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/CoTaskMemUnicodeSafeHandle.cs @@ -10,7 +10,7 @@ namespace System.Diagnostics.Eventing.Reader /// internal sealed class CoTaskMemUnicodeSafeHandle : SafeHandle { - internal CoTaskMemUnicodeSafeHandle() + public CoTaskMemUnicodeSafeHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/EventLogHandle.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/EventLogHandle.cs index 4424a5c9fbb20..87fe14a067c2a 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/EventLogHandle.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/Reader/EventLogHandle.cs @@ -11,8 +11,7 @@ namespace System.Diagnostics.Eventing.Reader /// internal sealed class EventLogHandle : SafeHandle { - // Called by P/Invoke when returning SafeHandles - private EventLogHandle() + public EventLogHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogReadHandle.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogReadHandle.cs index 680444c05728f..60a3f1304f0a8 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogReadHandle.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogReadHandle.cs @@ -5,7 +5,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEventLogReadHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeEventLogReadHandle() : base(true) { } + public SafeEventLogReadHandle() : base(true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogWriteHandle.cs b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogWriteHandle.cs index 7fb87283486ae..401ef8dec11d6 100644 --- a/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogWriteHandle.cs +++ b/src/libraries/System.Diagnostics.EventLog/src/System/Diagnostics/SafeEventLogWriteHandle.cs @@ -6,7 +6,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeEventLogWriteHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeEventLogWriteHandle() : base(true) { } + public SafeEventLogWriteHandle() : base(true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs index 4776804bc3dbb..3ff66cd763934 100644 --- a/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs +++ b/src/libraries/System.Diagnostics.Process/ref/System.Diagnostics.Process.cs @@ -8,6 +8,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeProcessHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public SafeProcessHandle() : base (default(bool)) { } public SafeProcessHandle(System.IntPtr existingHandle, bool ownsHandle) : base (default(bool)) { } protected override bool ReleaseHandle() { throw null; } } @@ -45,27 +46,27 @@ public Process() { } public System.IntPtr MaxWorkingSet { get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("macos")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("freebsd")] set { } } public System.IntPtr MinWorkingSet { get { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("macos")] [System.Runtime.Versioning.SupportedOSPlatformAttribute("freebsd")] set { } } public System.Diagnostics.ProcessModuleCollection Modules { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.NonpagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.NonpagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int NonpagedSystemMemorySize { get { throw null; } } public long NonpagedSystemMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PagedMemorySize { get { throw null; } } public long PagedMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PagedSystemMemorySize { get { throw null; } } public long PagedSystemMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakPagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakPagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakPagedMemorySize { get { throw null; } } public long PeakPagedMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakVirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakVirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakVirtualMemorySize { get { throw null; } } public long PeakVirtualMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakWorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakWorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakWorkingSet { get { throw null; } } public long PeakWorkingSet64 { get { throw null; } } public bool PriorityBoostEnabled { get { throw null; } set { } } public System.Diagnostics.ProcessPriorityClass PriorityClass { get { throw null; } set { } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PrivateMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PrivateMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PrivateMemorySize { get { throw null; } } public long PrivateMemorySize64 { get { throw null; } } public System.TimeSpan PrivilegedProcessorTime { get { throw null; } } @@ -83,10 +84,10 @@ public Process() { } public System.Diagnostics.ProcessThreadCollection Threads { get { throw null; } } public System.TimeSpan TotalProcessorTime { get { throw null; } } public System.TimeSpan UserProcessorTime { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.VirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.VirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int VirtualMemorySize { get { throw null; } } public long VirtualMemorySize64 { get { throw null; } } - [System.ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.WorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [System.ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.WorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int WorkingSet { get { throw null; } } public long WorkingSet64 { get { throw null; } } public event System.Diagnostics.DataReceivedEventHandler? ErrorDataReceived { add { } remove { } } diff --git a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs index 1b7511421676d..0175956501d9a 100644 --- a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs +++ b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.cs @@ -19,7 +19,7 @@ public sealed partial class SafeProcessHandle : SafeHandleZeroOrMinusOneIsInvali { internal static readonly SafeProcessHandle InvalidHandle = new SafeProcessHandle(); - internal SafeProcessHandle() + public SafeProcessHandle() : this(IntPtr.Zero) { } @@ -34,11 +34,5 @@ public SafeProcessHandle(IntPtr existingHandle, bool ownsHandle) { SetHandle(existingHandle); } - - internal void InitialSetHandle(IntPtr h) - { - Debug.Assert(IsInvalid, "Safe handle should only be set once"); - base.handle = h; - } } } diff --git a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeThreadHandle.cs b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeThreadHandle.cs index 055c4f6f2a465..34f3df7885f96 100644 --- a/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeThreadHandle.cs +++ b/src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeThreadHandle.cs @@ -20,17 +20,11 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeThreadHandle : SafeHandle { - internal SafeThreadHandle() + public SafeThreadHandle() : base(new IntPtr(0), true) { } - internal void InitialSetHandle(IntPtr h) - { - Debug.Assert(IsInvalid, "Safe handle should only be set once"); - base.SetHandle(h); - } - public override bool IsInvalid { get { return handle == IntPtr.Zero || handle == new IntPtr(-1); } diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs index e904dbbff06f4..f8da13f762a9d 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.Windows.cs @@ -610,7 +610,7 @@ ref processInfo // pointer to PROCESS_INFORMATION } if (processInfo.hProcess != IntPtr.Zero && processInfo.hProcess != new IntPtr(-1)) - procSH.InitialSetHandle(processInfo.hProcess); + Marshal.InitHandle(procSH, processInfo.hProcess); if (processInfo.hThread != IntPtr.Zero && processInfo.hThread != new IntPtr(-1)) Interop.Kernel32.CloseHandle(processInfo.hThread); diff --git a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index 53fd78776b083..9c540628ce827 100644 --- a/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -316,7 +316,7 @@ public long NonpagedSystemMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.NonpagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.NonpagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int NonpagedSystemMemorySize { get @@ -336,7 +336,7 @@ public long PagedMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PagedMemorySize { get @@ -356,7 +356,7 @@ public long PagedSystemMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PagedSystemMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PagedSystemMemorySize { get @@ -376,7 +376,7 @@ public long PeakPagedMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakPagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakPagedMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakPagedMemorySize { get @@ -395,7 +395,7 @@ public long PeakWorkingSet64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakWorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakWorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakWorkingSet { get @@ -414,7 +414,7 @@ public long PeakVirtualMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PeakVirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PeakVirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PeakVirtualMemorySize { get @@ -489,7 +489,7 @@ public long PrivateMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.PrivateMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.PrivateMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int PrivateMemorySize { get @@ -632,7 +632,7 @@ public long VirtualMemorySize64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.VirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.VirtualMemorySize64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int VirtualMemorySize { get @@ -752,7 +752,7 @@ public long WorkingSet64 } } - [ObsoleteAttribute("This property has been deprecated. Please use System.Diagnostics.Process.WorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] + [ObsoleteAttribute("This property has been deprecated because the type of the property can't represent all valid results. Please use System.Diagnostics.Process.WorkingSet64 instead. https://go.microsoft.com/fwlink/?linkid=14202")] public int WorkingSet { get diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs index c4f9a0bd7afcf..2383e816e1ddb 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Linux.cs @@ -9,7 +9,7 @@ internal sealed class ConnectionHandle : SafeHandleZeroOrMinusOneIsInvalid { internal bool _needDispose; - internal ConnectionHandle() + public ConnectionHandle() :base(true) { Interop.Ldap.ldap_initialize(out handle, null); @@ -44,7 +44,7 @@ protected override bool ReleaseHandle() internal sealed class SafeBerHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeBerHandle() : base(true) + public SafeBerHandle() : base(true) { SetHandle(Interop.Ldap.ber_alloc(1)); if (handle == IntPtr.Zero) diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Windows.cs index 725ac6be053d0..15f34fff77387 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Windows.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/Interop/SafeHandles.Windows.cs @@ -7,7 +7,7 @@ namespace System.DirectoryServices.Protocols { internal sealed class SafeBerHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeBerHandle() : base(true) + public SafeBerHandle() : base(true) { SetHandle(Interop.Ldap.ber_alloc(1)); if (handle == IntPtr.Zero) @@ -36,7 +36,7 @@ internal sealed class ConnectionHandle : SafeHandleZeroOrMinusOneIsInvalid { internal bool _needDispose; - internal ConnectionHandle() : base(true) + public ConnectionHandle() : base(true) { SetHandle(Interop.Ldap.ldap_init(null, 389)); diff --git a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/SafeHandle.cs b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/SafeHandle.cs index 1b47eaed3d483..af0b09820a11a 100644 --- a/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/SafeHandle.cs +++ b/src/libraries/System.DirectoryServices/src/System/DirectoryServices/ActiveDirectory/SafeHandle.cs @@ -8,6 +8,8 @@ namespace System.DirectoryServices.ActiveDirectory { internal sealed class PolicySafeHandle : SafeHandleZeroOrMinusOneIsInvalid { + public PolicySafeHandle() : base(true) { } + internal PolicySafeHandle(IntPtr value) : base(true) { SetHandle(value); @@ -18,7 +20,7 @@ internal PolicySafeHandle(IntPtr value) : base(true) internal sealed class LsaLogonProcessSafeHandle : SafeHandleZeroOrMinusOneIsInvalid { - private LsaLogonProcessSafeHandle() : base(true) { } + public LsaLogonProcessSafeHandle() : base(true) { } internal LsaLogonProcessSafeHandle(IntPtr value) : base(true) { @@ -30,7 +32,7 @@ internal LsaLogonProcessSafeHandle(IntPtr value) : base(true) internal sealed class LoadLibrarySafeHandle : SafeHandleZeroOrMinusOneIsInvalid { - private LoadLibrarySafeHandle() : base(true) { } + public LoadLibrarySafeHandle() : base(true) { } internal LoadLibrarySafeHandle(IntPtr value) : base(true) { diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs index a139927f622c2..beb8d42bb6ff5 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Drawing2D/SafeCustomLineCapHandle.cs @@ -9,7 +9,7 @@ namespace System.Drawing.Drawing2D { - internal class SafeCustomLineCapHandle : SafeHandle + internal sealed class SafeCustomLineCapHandle : SafeHandle { // Create a SafeHandle, informing the base class // that this SafeHandle instance "owns" the handle, diff --git a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintController.Windows.cs b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintController.Windows.cs index d3c9841bd1e13..e089027bf6067 100644 --- a/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintController.Windows.cs +++ b/src/libraries/System.Drawing.Common/src/System/Drawing/Printing/PrintController.Windows.cs @@ -20,12 +20,7 @@ public abstract partial class PrintController /// internal sealed class SafeDeviceModeHandle : SafeHandle { - /// - /// This constructor is used by the P/Invoke marshaling layer - /// to allocate a SafeHandle instance. P/Invoke then does the - /// appropriate method call, storing the handle in this class. - /// - private SafeDeviceModeHandle() : base(IntPtr.Zero, ownsHandle: true) + public SafeDeviceModeHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs index f1847b27655e3..4fa32f5127936 100644 --- a/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs +++ b/src/libraries/System.Globalization/tests/System/Globalization/RegionInfoTests.cs @@ -100,6 +100,7 @@ public void ValidateUsingCasedRegionName(string regionName) [Theory] [InlineData("en-US", "United States")] [OuterLoop("May fail on machines with multiple language packs installed")] // see https://github.com/dotnet/runtime/issues/30132 + [ActiveIssue("https://github.com/dotnet/runtime/issues/45951", TestPlatforms.Browser)] public void DisplayName(string name, string expected) { using (new ThreadCultureChange(null, new CultureInfo(name))) diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj b/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj index f6f195f6d42c0..2687d60160295 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj @@ -118,10 +118,8 @@ - - - + diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs index ed4f1639d07a7..ed924f39e7754 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs @@ -295,9 +295,12 @@ internal RunningInstance( internal void Start() { - // Schedule a task to read from the inotify queue and process the events. - Task.Factory.StartNew(obj => ((RunningInstance)obj!).ProcessEvents(), - this, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + // Spawn a thread to read from the inotify queue and process the events. + new Thread(obj => ((RunningInstance)obj!).ProcessEvents()) + { + IsBackground = true, + Name = ".NET File Watcher" + }.Start(this); // PERF: As needed, we can look into making this use async I/O rather than burning // a thread that blocks in the read syscall. diff --git a/src/libraries/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs b/src/libraries/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs index 99f9c98a30e9d..5993c1d6ac508 100644 --- a/src/libraries/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs +++ b/src/libraries/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs @@ -139,27 +139,6 @@ public static void MoveFile(string sourceFullPath, string destFullPath) public static void MoveFile(string sourceFullPath, string destFullPath, bool overwrite) { - // The desired behavior for Move(source, dest) is to not overwrite the destination file - // if it exists. Since rename(source, dest) will replace the file at 'dest' if it exists, - // link/unlink are used instead. Rename is more efficient than link/unlink on file systems - // where hard links are not supported (such as FAT). Therefore, given that source file exists, - // rename is used in 2 cases: when dest file does not exist or when source path and dest - // path refer to the same file (on the same device). This is important for case-insensitive - // file systems (e.g. renaming a file in a way that just changes casing), so that we support - // changing the casing in the naming of the file. If this fails in any way (e.g. source file - // doesn't exist, dest file doesn't exist, rename fails, etc.), we just fall back to trying the - // link/unlink approach and generating any exceptional messages from there as necessary. - Interop.Sys.FileStatus sourceStat, destStat; - if (Interop.Sys.LStat(sourceFullPath, out sourceStat) == 0 && // source file exists - (Interop.Sys.LStat(destFullPath, out destStat) != 0 || // dest file does not exist - (sourceStat.Dev == destStat.Dev && // source and dest are on the same device - sourceStat.Ino == destStat.Ino)) && // source and dest are the same file on that device - Interop.Sys.Rename(sourceFullPath, destFullPath) == 0) // try the rename - { - // Renamed successfully. - return; - } - // If overwrite is allowed then just call rename if (overwrite) { @@ -179,7 +158,7 @@ public static void MoveFile(string sourceFullPath, string destFullPath, bool ove // when the error occurs and our checks, but it's the best we can do, and the // worst case in such a race condition (which could occur if the file system is // being manipulated concurrently with these checks) is that we throw a - // FileNotFoundException instead of DirectoryNotFoundexception. + // FileNotFoundException instead of DirectoryNotFoundException. throw Interop.GetExceptionForIoErrno(errorInfo, destFullPath, isDirectory: errorInfo.Error == Interop.Error.ENOENT && !Directory.Exists(Path.GetDirectoryName(destFullPath)) // The parent directory of destFile can't be found ); @@ -190,6 +169,28 @@ public static void MoveFile(string sourceFullPath, string destFullPath, bool ove return; } + // The desired behavior for Move(source, dest) is to not overwrite the destination file + // if it exists. Since rename(source, dest) will replace the file at 'dest' if it exists, + // link/unlink are used instead. Rename is more efficient than link/unlink on file systems + // where hard links are not supported (such as FAT). Therefore, given that source file exists, + // rename is used in 2 cases: when dest file does not exist or when source path and dest + // path refer to the same file (on the same device). This is important for case-insensitive + // file systems (e.g. renaming a file in a way that just changes casing), so that we support + // changing the casing in the naming of the file. If this fails in any way (e.g. source file + // doesn't exist, dest file doesn't exist, rename fails, etc.), we just fall back to trying the + // link/unlink approach and generating any exceptional messages from there as necessary. + + Interop.Sys.FileStatus sourceStat, destStat; + if (Interop.Sys.LStat(sourceFullPath, out sourceStat) == 0 && // source file exists + (Interop.Sys.LStat(destFullPath, out destStat) != 0 || // dest file does not exist + (sourceStat.Dev == destStat.Dev && // source and dest are on the same device + sourceStat.Ino == destStat.Ino)) && // source and dest are the same file on that device + Interop.Sys.Rename(sourceFullPath, destFullPath) == 0) // try the rename + { + // Renamed successfully. + return; + } + LinkOrCopyFile(sourceFullPath, destFullPath); DeleteFile(sourceFullPath); } diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs index a5d9b5ee9952f..59a39d881ba5a 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytes.cs @@ -56,6 +56,7 @@ public void ValidWrite(int size) [Fact] [OuterLoop] + [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] public void ReadFileOver2GB() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs index 3c46acff8b4b7..471f950c14dc1 100644 --- a/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/File/ReadWriteAllBytesAsync.cs @@ -70,6 +70,7 @@ public Task AlreadyCanceledAsync() [Fact] [OuterLoop] + [ActiveIssue("https://github.com/dotnet/runtime/issues/45954", TestPlatforms.Browser)] public Task ReadFileOver2GBAsync() { string path = GetTestFilePath(); diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs index 170a890d79cb7..6ed4d1db2c4cc 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/WriteAsync.cs @@ -192,7 +192,7 @@ public Task ManyConcurrentWriteAsyncs() numWrites: 10); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [MemberData(nameof(MemberData_FileStreamAsyncWriting))] [OuterLoop] // many combinations: we test just one in inner loop and the rest outer public async Task ManyConcurrentWriteAsyncs_OuterLoop( diff --git a/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs b/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs index e112060f78498..2182c4a6d5b5a 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/ref/System.IO.MemoryMappedFiles.cs @@ -8,13 +8,13 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeMemoryMappedFileHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { - internal SafeMemoryMappedFileHandle() : base (default(bool)) { } + public SafeMemoryMappedFileHandle() : base (default(bool)) { } public override bool IsInvalid { get { throw null; } } protected override bool ReleaseHandle() { throw null; } } public sealed partial class SafeMemoryMappedViewHandle : System.Runtime.InteropServices.SafeBuffer { - internal SafeMemoryMappedViewHandle() : base (default(bool)) { } + public SafeMemoryMappedViewHandle() : base (default(bool)) { } protected override bool ReleaseHandle() { throw null; } } } diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/ILLink/ILLinkTrim_LibraryBuild.xml b/src/libraries/System.IO.MemoryMappedFiles/src/ILLink/ILLinkTrim_LibraryBuild.xml deleted file mode 100644 index 6c7d766e65f5c..0000000000000 --- a/src/libraries/System.IO.MemoryMappedFiles/src/ILLink/ILLinkTrim_LibraryBuild.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Windows.cs b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Windows.cs index bd0f7ebb13a38..7d58a373446c9 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Windows.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.Windows.cs @@ -5,11 +5,6 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeMemoryMappedFileHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeMemoryMappedFileHandle() - : base(true) - { - } - protected override bool ReleaseHandle() { return Interop.Kernel32.CloseHandle(handle); diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.cs b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.cs new file mode 100644 index 0000000000000..0c8da7eb7b8da --- /dev/null +++ b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedFileHandle.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.Runtime.InteropServices; + +namespace Microsoft.Win32.SafeHandles +{ + public sealed partial class SafeMemoryMappedFileHandle : SafeHandleZeroOrMinusOneIsInvalid + { + public SafeMemoryMappedFileHandle() + : base(true) + { + } + } +} diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedViewHandle.cs b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedViewHandle.cs index f3c065c07ca4b..8d8bc6f1ec4f2 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedViewHandle.cs +++ b/src/libraries/System.IO.MemoryMappedFiles/src/Microsoft/Win32/SafeMemoryMappedViewHandle.cs @@ -7,7 +7,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeMemoryMappedViewHandle : SafeBuffer { - internal SafeMemoryMappedViewHandle() + public SafeMemoryMappedViewHandle() : base(true) { } diff --git a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj index e0a5390f9a62f..bd15c52faebb1 100644 --- a/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj +++ b/src/libraries/System.IO.MemoryMappedFiles/src/System.IO.MemoryMappedFiles.csproj @@ -6,6 +6,7 @@ + diff --git a/src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs b/src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs index e7bb17903c68e..3966988ecad2d 100644 --- a/src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs +++ b/src/libraries/System.IO.Pipes/ref/System.IO.Pipes.cs @@ -8,6 +8,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafePipeHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public SafePipeHandle() : base (default(bool)) { } public SafePipeHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { } public override bool IsInvalid { get { throw null; } } protected override bool ReleaseHandle() { throw null; } diff --git a/src/libraries/System.IO.Pipes/src/Microsoft/Win32/SafeHandles/SafePipeHandle.cs b/src/libraries/System.IO.Pipes/src/Microsoft/Win32/SafeHandles/SafePipeHandle.cs index 18a06be8cbc71..df26c48b44c01 100644 --- a/src/libraries/System.IO.Pipes/src/Microsoft/Win32/SafeHandles/SafePipeHandle.cs +++ b/src/libraries/System.IO.Pipes/src/Microsoft/Win32/SafeHandles/SafePipeHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafePipeHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafePipeHandle() + public SafePipeHandle() : this(new IntPtr(DefaultInvalidHandle), true) { } diff --git a/src/libraries/System.IO.Ports/src/System/IO/Ports/SafeSerialDeviceHandle.Unix.cs b/src/libraries/System.IO.Ports/src/System/IO/Ports/SafeSerialDeviceHandle.Unix.cs index 7f3f02b47f947..4d2202b99abb8 100644 --- a/src/libraries/System.IO.Ports/src/System/IO/Ports/SafeSerialDeviceHandle.Unix.cs +++ b/src/libraries/System.IO.Ports/src/System/IO/Ports/SafeSerialDeviceHandle.Unix.cs @@ -12,7 +12,7 @@ namespace System.IO.Ports { internal sealed class SafeSerialDeviceHandle : SafeHandleMinusOneIsInvalid { - private SafeSerialDeviceHandle() : base(ownsHandle: true) + public SafeSerialDeviceHandle() : base(ownsHandle: true) { } diff --git a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs index 850f5c8feed31..fa1bfb3edf31d 100644 --- a/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs +++ b/src/libraries/System.Linq.Expressions/tests/CompilerTests.cs @@ -27,8 +27,9 @@ public static void CompileDeepTree_NoStackOverflow(bool useInterpreter) Assert.Equal(n, f()); } - [Theory, ClassData(typeof(CompilationTypes))] + [ClassData(typeof(CompilationTypes))] [OuterLoop("May fail with SO on Debug JIT")] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void CompileDeepTree_NoStackOverflowFast(bool useInterpreter) { Expression e = Expression.Constant(0); diff --git a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs index c3253aada63af..e93d91af3dadc 100644 --- a/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs +++ b/src/libraries/System.Linq.Queryable/ref/System.Linq.Queryable.cs @@ -162,6 +162,7 @@ public static partial class Queryable public static System.Linq.IQueryable Where(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static System.Linq.IQueryable Where(this System.Linq.IQueryable source, System.Linq.Expressions.Expression> predicate) { throw null; } public static System.Linq.IQueryable<(TFirst First, TSecond Second)> Zip(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2) { throw null; } + public static System.Linq.IQueryable<(TFirst First, TSecond Second, TThird Third)> Zip(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Collections.Generic.IEnumerable source3) { throw null; } public static System.Linq.IQueryable Zip(this System.Linq.IQueryable source1, System.Collections.Generic.IEnumerable source2, System.Linq.Expressions.Expression> resultSelector) { throw null; } } } diff --git a/src/libraries/System.Linq.Queryable/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Linq.Queryable/src/ILLink/ILLink.Suppressions.xml index 49a5c2e445c19..81bfe8dd96122 100644 --- a/src/libraries/System.Linq.Queryable/src/ILLink/ILLink.Suppressions.xml +++ b/src/libraries/System.Linq.Queryable/src/ILLink/ILLink.Suppressions.xml @@ -637,6 +637,12 @@ member M:System.Linq.CachedReflectionInfo.Zip_TFirst_TSecond_TResult_3(System.Type,System.Type,System.Type) + + ILLink + IL2060 + member + M:System.Linq.CachedReflectionInfo.Zip_TFirst_TSecond_TThird_3(System.Type,System.Type,System.Type) + ILLink IL2060 @@ -662,4 +668,4 @@ M:System.Linq.TypeHelper.GetStaticMethods(System.Type) - \ No newline at end of file + diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs index 8f345d0a86bb4..943a72b0b6d48 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/CachedReflection.cs @@ -843,6 +843,13 @@ public static MethodInfo Zip_TFirst_TSecond_TResult_3(Type TFirst, Type TSecond, (s_Zip_TFirst_TSecond_TResult_3 = new Func, IEnumerable, Expression>, IQueryable>(Queryable.Zip).GetMethodInfo().GetGenericMethodDefinition())) .MakeGenericMethod(TFirst, TSecond, TResult); + private static MethodInfo? s_Zip_TFirst_TSecond_TThird_3; + + public static MethodInfo Zip_TFirst_TSecond_TThird_3(Type TFirst, Type TSecond, Type TThird) => + (s_Zip_TFirst_TSecond_TThird_3 ?? + (s_Zip_TFirst_TSecond_TThird_3 = new Func, IEnumerable, IEnumerable, IQueryable<(object, object, object)>>(Queryable.Zip).GetMethodInfo().GetGenericMethodDefinition())) + .MakeGenericMethod(TFirst, TSecond, TThird); + private static MethodInfo? s_SkipLast_TSource_2; diff --git a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs index dab995cba60f0..7827249013cd0 100644 --- a/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs +++ b/src/libraries/System.Linq.Queryable/src/System/Linq/Queryable.cs @@ -666,6 +666,33 @@ public static IQueryable Zip(this IQueryable< )); } + /// + /// Produces a sequence of tuples with elements from the three specified sequences. + /// + /// The type of the elements of the first input sequence. + /// The type of the elements of the second input sequence. + /// The type of the elements of the third input sequence. + /// The first sequence to merge. + /// The second sequence to merge. + /// The third sequence to merge. + /// A sequence of tuples with elements taken from the first, second and third sequences, in that order. + [DynamicDependency("Zip`3", typeof(Enumerable))] + public static IQueryable<(TFirst First, TSecond Second, TThird Third)> Zip(this IQueryable source1, IEnumerable source2, IEnumerable source3) + { + if (source1 == null) + throw Error.ArgumentNull(nameof(source1)); + if (source2 == null) + throw Error.ArgumentNull(nameof(source2)); + if (source3 == null) + throw Error.ArgumentNull(nameof(source3)); + return source1.Provider.CreateQuery<(TFirst, TSecond, TThird)>( + Expression.Call( + null, + CachedReflectionInfo.Zip_TFirst_TSecond_TThird_3(typeof(TFirst), typeof(TSecond), typeof(TThird)), + source1.Expression, GetSourceExpression(source2), GetSourceExpression(source3) + )); + } + [DynamicDependency("Union`1", typeof(Enumerable))] public static IQueryable Union(this IQueryable source1, IEnumerable source2) { diff --git a/src/libraries/System.Linq.Queryable/tests/ZipTests.cs b/src/libraries/System.Linq.Queryable/tests/ZipTests.cs index 186ef07c41908..26498693a2988 100644 --- a/src/libraries/System.Linq.Queryable/tests/ZipTests.cs +++ b/src/libraries/System.Linq.Queryable/tests/ZipTests.cs @@ -90,5 +90,55 @@ public void TupleNames() Assert.Equal(tuple.Item1, tuple.First); Assert.Equal(tuple.Item2, tuple.Second); } + + [Fact] + public void Zip3_CorrectResults() + { + int[] first = new int[] { 1, 3, 5 }; + int[] second = new int[] { 2, 6, 8 }; + int[] third = new int[] { 1, 7, 2 }; + var expected = new (int, int, int)[] { (1, 2, 1), (3, 6, 7), (5, 8, 2) }; + Assert.Equal(expected, first.AsQueryable().Zip(second.AsQueryable(), third.AsQueryable())); + } + + + [Fact] + public void Zip3_FirstIsNull() + { + IQueryable first = null; + int[] second = new int[] { 2, 6, 8 }; + int[] third = new int[] { 1, 7, 2 }; + AssertExtensions.Throws("source1", () => first.Zip(second.AsQueryable(), third.AsQueryable())); + } + + [Fact] + public void Zip3_SecondIsNull() + { + int[] first = new int[] { 1, 3, 5 }; + IQueryable second = null; + int[] third = new int[] { 1, 7, 2 }; + AssertExtensions.Throws("source2", () => first.AsQueryable().Zip(second, third.AsQueryable())); + } + + [Fact] + public void Zip3_ThirdIsNull() + { + int[] first = new int[] { 1, 3, 5 }; + int[] second = new int[] { 2, 6, 8 }; + IQueryable third = null; + AssertExtensions.Throws("source3", () => first.AsQueryable().Zip(second.AsQueryable(), third)); + } + + [Fact] + public void Zip3_TupleNames() + { + int[] first = new int[] { 1 }; + int[] second = new int[] { 2 }; + int[] third = new int[] { 3 }; + var tuple = first.AsQueryable().Zip(second.AsQueryable(), third.AsQueryable()).First(); + Assert.Equal(tuple.Item1, tuple.First); + Assert.Equal(tuple.Item2, tuple.Second); + Assert.Equal(tuple.Item3, tuple.Third); + } } } diff --git a/src/libraries/System.Linq/ref/System.Linq.cs b/src/libraries/System.Linq/ref/System.Linq.cs index 3c2a7ec8b24c6..306886754a23d 100644 --- a/src/libraries/System.Linq/ref/System.Linq.cs +++ b/src/libraries/System.Linq/ref/System.Linq.cs @@ -193,6 +193,7 @@ public static System.Collections.Generic.IEnumerable< public static System.Collections.Generic.IEnumerable Where(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static System.Collections.Generic.IEnumerable Where(this System.Collections.Generic.IEnumerable source, System.Func predicate) { throw null; } public static System.Collections.Generic.IEnumerable<(TFirst First, TSecond Second)> Zip(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second) { throw null; } + public static System.Collections.Generic.IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Collections.Generic.IEnumerable third) { throw null; } public static System.Collections.Generic.IEnumerable Zip(this System.Collections.Generic.IEnumerable first, System.Collections.Generic.IEnumerable second, System.Func resultSelector) { throw null; } } public partial interface IGrouping : System.Collections.Generic.IEnumerable, System.Collections.IEnumerable diff --git a/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs b/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs index 48a1052a277eb..de3c1300f1a94 100644 --- a/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs +++ b/src/libraries/System.Linq/src/System/Linq/ThrowHelper.cs @@ -50,6 +50,7 @@ private static string GetArgumentString(ExceptionArgument argument) case ExceptionArgument.second: return nameof(ExceptionArgument.second); case ExceptionArgument.selector: return nameof(ExceptionArgument.selector); case ExceptionArgument.source: return nameof(ExceptionArgument.source); + case ExceptionArgument.third: return nameof(ExceptionArgument.third); default: Debug.Fail("The ExceptionArgument value is not defined."); return string.Empty; @@ -76,5 +77,6 @@ internal enum ExceptionArgument second, selector, source, + third, } } diff --git a/src/libraries/System.Linq/src/System/Linq/Zip.cs b/src/libraries/System.Linq/src/System/Linq/Zip.cs index 804f98b5db11e..d0a380086d228 100644 --- a/src/libraries/System.Linq/src/System/Linq/Zip.cs +++ b/src/libraries/System.Linq/src/System/Linq/Zip.cs @@ -42,6 +42,36 @@ public static IEnumerable Zip(this IEnumerabl return ZipIterator(first, second); } + /// + /// Produces a sequence of tuples with elements from the three specified sequences. + /// + /// The type of the elements of the first input sequence. + /// The type of the elements of the second input sequence. + /// The type of the elements of the third input sequence. + /// The first sequence to merge. + /// The second sequence to merge. + /// The third sequence to merge. + /// A sequence of tuples with elements taken from the first, second, and third sequences, in that order. + public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip(this IEnumerable first, IEnumerable second, IEnumerable third) + { + if (first is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.first); + } + + if (second is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.second); + } + + if (third is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.third); + } + + return ZipIterator(first, second, third); + } + private static IEnumerable<(TFirst First, TSecond Second)> ZipIterator(IEnumerable first, IEnumerable second) { using (IEnumerator e1 = first.GetEnumerator()) @@ -65,5 +95,18 @@ private static IEnumerable ZipIterator(IEnume } } } + + private static IEnumerable<(TFirst First, TSecond Second, TThird Third)> ZipIterator(IEnumerable first, IEnumerable second, IEnumerable third) + { + using (IEnumerator e1 = first.GetEnumerator()) + using (IEnumerator e2 = second.GetEnumerator()) + using (IEnumerator e3 = third.GetEnumerator()) + { + while (e1.MoveNext() && e2.MoveNext() && e3.MoveNext()) + { + yield return (e1.Current, e2.Current, e3.Current); + } + } + } } } diff --git a/src/libraries/System.Linq/tests/ZipTests.cs b/src/libraries/System.Linq/tests/ZipTests.cs index 628201460666c..cfaa8bade6577 100644 --- a/src/libraries/System.Linq/tests/ZipTests.cs +++ b/src/libraries/System.Linq/tests/ZipTests.cs @@ -393,7 +393,7 @@ public void Zip2_ImplicitTypeParameters() { IEnumerable first = new int[] { 1, 2, 3 }; IEnumerable second = new int[] { 2, 5, 9 }; - IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 5), (3, 9) }; Assert.Equal(expected, first.Zip(second)); } @@ -403,7 +403,7 @@ public void Zip2_ExplicitTypeParameters() { IEnumerable first = new int[] { 1, 2, 3 }; IEnumerable second = new int[] { 2, 5, 9 }; - IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (2,5), (3,9) }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (2, 5), (3, 9) }; Assert.Equal(expected, first.Zip(second)); } @@ -431,7 +431,7 @@ public void Zip2_ExceptionThrownFromFirstsEnumerator() { ThrowsOnMatchEnumerable first = new ThrowsOnMatchEnumerable(new int[] { 1, 3, 3 }, 2); IEnumerable second = new int[] { 2, 4, 6 }; - IEnumerable<(int, int)> expected = new (int,int)[] { (1,2), (3,4), (3,6) }; + IEnumerable<(int, int)> expected = new (int, int)[] { (1, 2), (3, 4), (3, 6) }; Assert.Equal(expected, first.Zip(second)); @@ -447,7 +447,7 @@ public void Zip2_ExceptionThrownFromSecondsEnumerator() { ThrowsOnMatchEnumerable second = new ThrowsOnMatchEnumerable(new int[] { 1, 3, 3 }, 2); IEnumerable first = new int[] { 2, 4, 6 }; - IEnumerable<(int, int)> expected = new (int,int)[] { (2,1), (4,3), (6,3) }; + IEnumerable<(int, int)> expected = new (int, int)[] { (2, 1), (4, 3), (6, 3) }; Assert.Equal(expected, first.Zip(second)); @@ -601,5 +601,123 @@ public void Zip2_TupleNames() Assert.Equal(t.Item1, t.First); Assert.Equal(t.Item2, t.Second); } + + [Fact] + public void Zip3_FirstIsNull() + { + IEnumerable first = null; + IEnumerable second = new[] { 4, 5, 6 }; + IEnumerable third = new[] { 7, 8, 9 }; + + AssertExtensions.Throws("first", () => first.Zip(second, third)); + } + + [Fact] + public void Zip3_SecondIsNull() + { + IEnumerable first = new[] { 1, 2, 3 }; + IEnumerable second = null; + IEnumerable third = new[] { 4, 5, 6 }; + + AssertExtensions.Throws("second", () => first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdIsNull() + { + IEnumerable first = new[] { 1, 2, 3 }; + IEnumerable second = new[] { 4, 5, 6 }; + IEnumerable third = null; + + AssertExtensions.Throws("third", () => first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdEmpty() + { + IEnumerable first = new[] { 1, 2, 3 }; + IEnumerable second = new[] { 4, 5, 6 }; + IEnumerable third = new int[] { }; + IEnumerable<(int, int, int)> expected = new (int, int, int)[] { }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ImplicitTypeParameters() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5, 6 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ExplicitTypeParameters() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5, 6 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdOneMore() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5, 6, 7 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdManyMore() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5, 6, 7, 8 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdOneLess() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_ThirdManyLess() + { + IEnumerable first = new[] { 1, 2, 3 }; + IEnumerable second = new[] { 3, 4, 5 }; + IEnumerable third = new[] { 5 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5) }; + + Assert.Equal(expected, first.Zip(second, third)); + } + + [Fact] + public void Zip3_RunOnce() + { + IEnumerable first = new[] { 1, 2 }; + IEnumerable second = new[] { 3, 4 }; + IEnumerable third = new[] { 5, 6 }; + IEnumerable<(int, int, int)> expected = new[] { (1, 3, 5), (2, 4, 6) }; + + Assert.Equal(expected, first.RunOnce().Zip(second.RunOnce(), third.RunOnce())); + } } } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj index 4bbd2f502e116..58aa20055a8fc 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj @@ -97,6 +97,8 @@ Link="Common\System\Net\Http\HttpClientHandlerTest.MaxResponseHeadersLength.cs" /> + in ' [""]' @@ -198,13 +196,10 @@ internal static int GetWarningLength(string? input, int startIndex, out object? return 0; } - WarningHeaderValue result = new WarningHeaderValue(); - result._code = code; - result._agent = agent; - result._text = input.Substring(textStartIndex, textLength); - result._date = date; + parsedValue = date is null ? + new WarningHeaderValue(code, agent, text) : + new WarningHeaderValue(code, agent, text, date.Value); - parsedValue = result; return current - startIndex; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs index ccbae0f866706..270d76a1cb40e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpMethod.cs @@ -23,19 +23,6 @@ public class HttpMethod : IEquatable private static readonly HttpMethod s_patchMethod = new HttpMethod("PATCH"); private static readonly HttpMethod s_connectMethod = new HttpMethod("CONNECT", http3StaticTableIndex: H3StaticTable.MethodConnect); - private static readonly Dictionary s_knownMethods = new Dictionary(9) - { - { s_getMethod, s_getMethod }, - { s_putMethod, s_putMethod }, - { s_postMethod, s_postMethod }, - { s_deleteMethod, s_deleteMethod }, - { s_headMethod, s_headMethod }, - { s_optionsMethod, s_optionsMethod }, - { s_traceMethod, s_traceMethod }, - { s_patchMethod, s_patchMethod }, - { s_connectMethod, s_connectMethod }, - }; - public static HttpMethod Get { get { return s_getMethod; } @@ -175,13 +162,39 @@ public override string ToString() /// internal static HttpMethod Normalize(HttpMethod method) { - // _http3EncodedBytes is only set for the singleton instances, so if it's not null, - // we can avoid the dictionary lookup. Otherwise, look up the method instance in the - // dictionary and return the normalized instance if it's found. Debug.Assert(method != null); - return - method._http3EncodedBytes is null && s_knownMethods.TryGetValue(method, out HttpMethod? normalized) ? normalized : - method; + Debug.Assert(!string.IsNullOrEmpty(method._method)); + + // _http3EncodedBytes is only set for the singleton instances, so if it's not null, + // we can avoid the lookup. Otherwise, look up the method instance and return the + // normalized instance if it's found. + + if (method._http3EncodedBytes is null && method._method.Length >= 3) // 3 == smallest known method + { + HttpMethod? match = (method._method[0] | 0x20) switch + { + 'c' => s_connectMethod, + 'd' => s_deleteMethod, + 'g' => s_getMethod, + 'h' => s_headMethod, + 'o' => s_optionsMethod, + 'p' => method._method.Length switch + { + 3 => s_putMethod, + 4 => s_postMethod, + _ => s_patchMethod, + }, + 't' => s_traceMethod, + _ => null, + }; + + if (match is not null && string.Equals(method._method, match._method, StringComparison.OrdinalIgnoreCase)) + { + return match; + } + } + + return method; } internal bool MustHaveRequestBody 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 5f0c3cf885be5..5dd39650f256a 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 @@ -15,8 +15,7 @@ namespace System.Net.Http { internal sealed class Http3Connection : HttpConnectionBase, IDisposable { - // TODO: once HTTP/3 is standardized, create APIs for these. - public static readonly Version HttpVersion30 = new Version(3, 0); + // TODO: once HTTP/3 is standardized, create APIs for this. public static readonly SslApplicationProtocol Http3ApplicationProtocol = new SslApplicationProtocol("h3-29"); private readonly HttpConnectionPool _pool; 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 ad85cbd6ce558..0830808fac5ee 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 @@ -889,7 +889,7 @@ private void OnHeader(int? staticIndex, HeaderDescriptor descriptor, string? sta _response = new HttpResponseMessage() { - Version = Http3Connection.HttpVersion30, + Version = HttpVersion.Version30, RequestMessage = _request, Content = new HttpConnectionResponseContent(), StatusCode = (HttpStatusCode)statusCode 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 8af9648b25907..480c77347ff39 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 @@ -120,7 +120,7 @@ public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionK } _http2Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version20; - _http3Enabled = _poolManager.Settings._maxHttpVersion >= Http3Connection.HttpVersion30 && (_poolManager.Settings._quicImplementationProvider ?? QuicImplementationProviders.Default).IsSupported; + _http3Enabled = _poolManager.Settings._maxHttpVersion >= HttpVersion.Version30 && (_poolManager.Settings._quicImplementationProvider ?? QuicImplementationProviders.Default).IsSupported; switch (kind) { diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index afa5b26d1a9d0..88e70ede696e1 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -70,7 +70,7 @@ public HttpConnectionSettings() { bool allowHttp2 = AllowHttp2; _maxHttpVersion = - AllowDraftHttp3 && allowHttp2 ? Http3Connection.HttpVersion30 : + AllowDraftHttp3 && allowHttp2 ? HttpVersion.Version30 : allowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11; _defaultCredentialsUsedForProxy = _proxy != null && (_proxy.Credentials == CredentialCache.DefaultCredentials || _defaultProxyCredentials == CredentialCache.DefaultCredentials); diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs index 8986911aa0bed..1ba84c9ebf6e9 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AltSvc.cs @@ -20,7 +20,7 @@ public HttpClientHandler_AltSvc_Test(ITestOutputHelper output) : base(output) /// protected override HttpClient CreateHttpClient() { - HttpClientHandler handler = CreateHttpClientHandler(HttpVersion30); + HttpClientHandler handler = CreateHttpClientHandler(HttpVersion.Version30); return CreateHttpClient(handler); } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs index 0bf7745d61053..53e81d91a7aa2 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs @@ -71,7 +71,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => [Fact] public async Task SendAsync_LargeHeaders_CorrectlyWritten() { - if (UseVersion == HttpVersion30) + if (UseVersion == HttpVersion.Version30) { // TODO: ActiveIssue return; @@ -338,7 +338,7 @@ await LoopbackServerFactory.CreateClientAndServerAsync(async uri => [InlineData(true)] public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort) { - if (UseVersion == HttpVersion30) + if (UseVersion == HttpVersion.Version30) { // External servers do not support HTTP3 currently. return; diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index 3b545edef8cce..f73942b6c011f 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -19,7 +19,7 @@ namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandlerTest_Http3 : HttpClientHandlerTestBase { - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; public HttpClientHandlerTest_Http3(ITestOutputHelper output) : base(output) { @@ -35,7 +35,7 @@ public async Task Public_Interop_ExactVersion_Success(string uri) { Method = HttpMethod.Get, RequestUri = new Uri(uri, UriKind.Absolute), - Version = HttpVersion30, + Version = HttpVersion.Version30, VersionPolicy = HttpVersionPolicy.RequestVersionExact }; using HttpResponseMessage response = await client.SendAsync(request).TimeoutAfter(20_000); @@ -57,7 +57,7 @@ public async Task Public_Interop_Upgrade_Success(string uri) { Method = HttpMethod.Get, RequestUri = new Uri(uri, UriKind.Absolute), - Version = HttpVersion30, + Version = HttpVersion.Version30, VersionPolicy = HttpVersionPolicy.RequestVersionOrLower }) { @@ -72,7 +72,7 @@ public async Task Public_Interop_Upgrade_Success(string uri) { Method = HttpMethod.Get, RequestUri = new Uri(uri, UriKind.Absolute), - Version = HttpVersion30, + Version = HttpVersion.Version30, VersionPolicy = HttpVersionPolicy.RequestVersionOrLower }) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs index dfbf00f0921f4..fd06954a523df 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.SocketsHttpHandler.cs @@ -22,7 +22,7 @@ protected static HttpClientHandler CreateHttpClientHandler(Version useVersion = { useVersion ??= HttpVersion.Version11; - HttpClientHandler handler = (PlatformDetection.SupportsAlpn && useVersion != HttpVersion30) ? new HttpClientHandler() : new VersionHttpClientHandler(useVersion); + HttpClientHandler handler = (PlatformDetection.SupportsAlpn && useVersion != HttpVersion.Version30) ? new HttpClientHandler() : new VersionHttpClientHandler(useVersion); if (useVersion >= HttpVersion.Version20) { diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs index 2c59a257911aa..e42252ed078b8 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs @@ -36,7 +36,7 @@ public void CreateAndDestroyManyClients(int numClients) public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3_MsQuic : HttpClientMiniStress { public SocketsHttpHandler_HttpClientMiniStress_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } @@ -44,7 +44,7 @@ public SocketsHttpHandler_HttpClientMiniStress_Http3_MsQuic(ITestOutputHelper ou public sealed class SocketsHttpHandler_HttpClientMiniStress_Http3_Mock : HttpClientMiniStress { public SocketsHttpHandler_HttpClientMiniStress_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs index a267ced0b67cc..3fda22015fb85 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientTest.cs @@ -1119,9 +1119,9 @@ public static IEnumerable VersionSelectionMemberData() yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, useSsl ? (object)HttpVersion.Version20 : typeof(HttpRequestException) }; if (QuicImplementationProviders.Default.IsSupported) { - yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrLower, HttpVersion30, useSsl, HttpVersion.Version11 }; - yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionExact, HttpVersion30, useSsl, HttpVersion.Version11 }; - yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion30, useSsl, useSsl ? HttpVersion30 : HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version30, useSsl, HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version30, useSsl, HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version11, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version30, useSsl, useSsl ? HttpVersion.Version30 : HttpVersion.Version11 }; } yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version11, useSsl, HttpVersion.Version11 }; @@ -1132,22 +1132,22 @@ public static IEnumerable VersionSelectionMemberData() yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, HttpVersion.Version20 }; if (QuicImplementationProviders.Default.IsSupported) { - yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrLower, HttpVersion30, useSsl, useSsl ? HttpVersion.Version20 : HttpVersion.Version11 }; - yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionExact, HttpVersion30, useSsl, HttpVersion.Version20 }; - yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion30, useSsl, useSsl ? (object)HttpVersion30 : typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version30, useSsl, useSsl ? HttpVersion.Version20 : HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version30, useSsl, HttpVersion.Version20 }; + yield return new object[] { HttpVersion.Version20, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version30, useSsl, useSsl ? (object)HttpVersion.Version30 : typeof(HttpRequestException) }; } if (QuicImplementationProviders.Default.IsSupported) { - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version11, useSsl, useSsl ? HttpVersion30 : HttpVersion.Version11 }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version11, useSsl, typeof(HttpRequestException) }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version11, useSsl, typeof(HttpRequestException) }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version20, useSsl, useSsl ? HttpVersion30 : HttpVersion.Version11 }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version20, useSsl, typeof(HttpRequestException) }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, typeof(HttpRequestException) }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion30, useSsl, useSsl ? HttpVersion30 : HttpVersion.Version11 }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionExact, HttpVersion30, useSsl, useSsl ? (object)HttpVersion30 : typeof(HttpRequestException) }; - yield return new object[] { HttpVersion30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion30, useSsl, useSsl ? (object)HttpVersion30 : typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version11, useSsl, useSsl ? HttpVersion.Version30 : HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version11, useSsl, typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version11, useSsl, typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version20, useSsl, useSsl ? HttpVersion.Version30 : HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version20, useSsl, typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version20, useSsl, typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrLower, HttpVersion.Version30, useSsl, useSsl ? HttpVersion.Version30 : HttpVersion.Version11 }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionExact, HttpVersion.Version30, useSsl, useSsl ? (object)HttpVersion.Version30 : typeof(HttpRequestException) }; + yield return new object[] { HttpVersion.Version30, HttpVersionPolicy.RequestVersionOrHigher, HttpVersion.Version30, useSsl, useSsl ? (object)HttpVersion.Version30 : typeof(HttpRequestException) }; } } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index 11cbe290a66d7..2369bc3fb1804 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -3026,14 +3026,14 @@ public SocketsHttpHandlerTest_Http3(ITestOutputHelper output) : base(output) { } public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_MsQuic : HttpClientHandlerTest { public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_Mock : HttpClientHandlerTest { public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } @@ -3042,14 +3042,14 @@ public SocketsHttpHandlerTest_HttpClientHandlerTest_Http3_Mock(ITestOutputHelper public sealed class SocketsHttpHandlerTest_Cookies_Http3_MsQuic : HttpClientHandlerTest_Cookies { public SocketsHttpHandlerTest_Cookies_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandlerTest_Cookies_Http3_Mock : HttpClientHandlerTest_Cookies { public SocketsHttpHandlerTest_Cookies_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } #endif @@ -3058,14 +3058,14 @@ public SocketsHttpHandlerTest_Cookies_Http3_Mock(ITestOutputHelper output) : bas public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_MsQuic : HttpClientHandlerTest_Headers { public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_Mock : HttpClientHandlerTest_Headers { public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } @@ -3074,14 +3074,14 @@ public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http3_Mock(ITestOutp public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_MsQuic : HttpClientHandler_Cancellation_Test { public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock : HttpClientHandler_Cancellation_Test { public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } #endif @@ -3091,14 +3091,14 @@ public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http3_Mock(ITestOu public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_MsQuic : HttpClientHandler_AltSvc_Test { public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_Mock : HttpClientHandler_AltSvc_Test { public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } #endif @@ -3107,14 +3107,14 @@ public SocketsHttpHandler_HttpClientHandler_AltSvc_Test_Http3_Mock(ITestOutputHe public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_MsQuic : HttpClientHandler_Finalization_Test { public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_MsQuic(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.MsQuic; } public sealed class SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Mock : HttpClientHandler_Finalization_Test { public SocketsHttpHandler_HttpClientHandler_Finalization_Http3_Mock(ITestOutputHelper output) : base(output) { } - protected override Version UseVersion => HttpVersion30; + protected override Version UseVersion => HttpVersion.Version30; protected override QuicImplementationProvider UseQuicImplementationProvider => QuicImplementationProviders.Mock; } } 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 bed4ab3c97d4b..2799f14f74475 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 @@ -116,6 +116,8 @@ + diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs index d9fca390f9396..2a0f211808b99 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs @@ -153,8 +153,8 @@ public void PopulateWithRandomHeaders(HttpRequestHeaders headers) string CreateHeaderValue() => HttpUtility.UrlEncode(GetRandomString(1, 30, alphaNumericOnly: false)); string[] values = Enumerable.Range(0, _random.Next(1, 6)).Select(_ => CreateHeaderValue()).ToArray(); totalSize += name.Length + values.Select(v => v.Length + 2).Sum(); - - if (totalSize > MaxRequestHeaderTotalSize) + + if (totalSize > MaxRequestHeaderTotalSize) { break; } @@ -189,7 +189,7 @@ public static (string name, Func operation)[] Operations = using var req = new HttpRequestMessage(HttpMethod.Get, "/get"); int expectedLength = ctx.SetExpectedResponseContentLengthHeader(req.Headers); using HttpResponseMessage m = await ctx.SendAsync(req); - + ValidateStatusCode(m); ValidateServerContent(await m.Content.ReadAsStringAsync(), expectedLength); }), @@ -282,7 +282,7 @@ public static (string name, Func operation)[] Operations = { using var req = new HttpRequestMessage(HttpMethod.Get, "/abort"); ctx.SetExpectedResponseContentLengthHeader(req.Headers, minLength: 2); - + await ctx.SendAsync(req); throw new Exception("Completed unexpectedly"); @@ -307,7 +307,7 @@ public static (string name, Func operation)[] Operations = case "Http2ProtocolException": case "Http2ConnectionException": case "Http2StreamException": - if ((e.InnerException?.Message?.Contains("INTERNAL_ERROR") ?? false) || // UseKestrel (https://github.com/aspnet/AspNetCore/issues/12256) + if ((e.InnerException?.Message?.Contains("INTERNAL_ERROR") ?? false) || // UseKestrel (https://github.com/dotnet/aspnetcore/issues/12256) (e.InnerException?.Message?.Contains("CANCEL") ?? false)) // UseHttpSys { return; @@ -490,7 +490,7 @@ private static void ValidateContent(string expectedContent, string actualContent { if (actualContent != expectedContent) { - int divergentIndex = + int divergentIndex = Enumerable .Zip(actualContent, expectedContent) .Select((x,i) => (x.First, x.Second, i)) diff --git a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs index 91361c140a4d7..1d5dab37ecba3 100644 --- a/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs +++ b/src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs @@ -67,7 +67,7 @@ public StressServer(Configuration configuration) // Use Kestrel, and configure it for HTTPS with a self-signed test certificate. host = host.UseKestrel(ko => { - // conservative estimation based on https://github.com/aspnet/AspNetCore/blob/caa910ceeba5f2b2c02c47a23ead0ca31caea6f0/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs#L204 + // conservative estimation based on https://github.com/dotnet/aspnetcore/blob/caa910ceeba5f2b2c02c47a23ead0ca31caea6f0/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Stream.cs#L204 ko.Limits.MaxRequestLineSize = Math.Max(ko.Limits.MaxRequestLineSize, configuration.MaxRequestUriSize + 100); ko.Limits.MaxRequestHeaderCount = Math.Max(ko.Limits.MaxRequestHeaderCount, configuration.MaxRequestHeaderCount); ko.Limits.MaxRequestHeadersTotalSize = Math.Max(ko.Limits.MaxRequestHeadersTotalSize, configuration.MaxRequestHeaderTotalSize); @@ -111,7 +111,7 @@ void ConfigureListenOptions(ListenOptions listenOptions) } else { - listenOptions.Protocols = + listenOptions.Protocols = configuration.HttpVersion == new Version(2,0) ? HttpProtocols.Http2 : HttpProtocols.Http1 ; @@ -123,7 +123,7 @@ void ConfigureListenOptions(ListenOptions listenOptions) LoggerConfiguration loggerConfiguration = new LoggerConfiguration(); if (configuration.Trace) { - // Clear existing logs first. + // Clear existing logs first. foreach (var filename in Directory.GetFiles(".", "server*.log")) { try @@ -239,7 +239,7 @@ private static void MapRoutes(IEndpointRouteBuilder endpoints) // Post echos back the requested content, first buffering it all server-side, then sending it all back. var s = new MemoryStream(); await context.Request.Body.CopyToAsync(s); - + ulong checksum = CRC.CalculateCRC(s.ToArray()); AppendChecksumHeader(context.Response.Headers, checksum); @@ -323,7 +323,7 @@ private static (string scheme, string hostname, int port) ParseServerUri(string { var uri = new Uri(serverUri); return (uri.Scheme, uri.Host, uri.Port); - } + } catch (UriFormatException) { // Simple uri parser: used to parse values valid in Kestrel @@ -345,7 +345,7 @@ private static string CreateResponseContent(HttpContext ctx) int GetExpectedContentLength() { - if (ctx.Request.Headers.TryGetValue(ExpectedResponseContentLength, out StringValues values) && + if (ctx.Request.Headers.TryGetValue(ExpectedResponseContentLength, out StringValues values) && values.Count == 1 && int.TryParse(values[0], out int result)) { diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestQueueV2Handle.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestQueueV2Handle.cs index c2a480306077a..98fdf54908cc4 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestQueueV2Handle.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/HttpRequestQueueV2Handle.cs @@ -8,7 +8,7 @@ namespace System.Net { internal sealed class HttpRequestQueueV2Handle : SafeHandleZeroOrMinusOneIsInvalid { - private HttpRequestQueueV2Handle() : base(true) + public HttpRequestQueueV2Handle() : base(true) { } diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/SafeWebSocketHandle.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/SafeWebSocketHandle.cs index 5ad684dfc8a12..90ad547f5a7b1 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/SafeWebSocketHandle.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Windows/WebSockets/SafeWebSocketHandle.cs @@ -9,7 +9,7 @@ namespace System.Net.WebSockets // but we use a SafeHandle because it provides us the guarantee that WebSocketDeleteHandle will always get called. internal sealed class SafeWebSocketHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeWebSocketHandle() + public SafeWebSocketHandle() : base(true) { } diff --git a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoggingTest.cs b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoggingTest.cs index dacc47eaae2af..915067acffbbf 100644 --- a/src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoggingTest.cs +++ b/src/libraries/System.Net.NameResolution/tests/FunctionalTests/LoggingTest.cs @@ -63,7 +63,6 @@ public void GetHostEntry_InvalidHost_LogsError() } [ConditionalFact] - [PlatformSpecific(~TestPlatforms.Windows)] // Unreliable on Windows. public async Task GetHostEntryAsync_InvalidHost_LogsError() { using (var listener = new TestEventListener("Private.InternalDiagnostics.System.Net.NameResolution", EventLevel.Error)) @@ -79,8 +78,7 @@ await listener.RunWithCallbackAsync(ev => events.Enqueue(ev), async () => } catch (SocketException e) when (e.SocketErrorCode == SocketError.HostNotFound) { - // Wait a bit to let the event source write it's log - await Task.Delay(100).ConfigureAwait(false); + await WaitForErrorEventAsync(events); } catch (Exception e) { @@ -97,6 +95,20 @@ await listener.RunWithCallbackAsync(ev => events.Enqueue(ev), async () => Assert.NotNull(ev.Payload[2]); } } + + static async Task WaitForErrorEventAsync(ConcurrentQueue events) + { + const int ErrorEventId = 5; + DateTime startTime = DateTime.UtcNow; + + while (!events.Any(e => e.EventId == ErrorEventId)) + { + if (DateTime.UtcNow.Subtract(startTime) > TimeSpan.FromSeconds(30)) + throw new TimeoutException("Timeout waiting for error event"); + + await Task.Delay(100); + } + } } [ConditionalFact] diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Unix.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Unix.cs index ac4ccb1b36d31..dd82f57c9a202 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Unix.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/NetworkAddressChange.Unix.cs @@ -150,8 +150,11 @@ private static void CreateSocket() } s_socket = newSocket; - Task.Factory.StartNew(s => LoopReadSocket((int)s!), s_socket, - CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); + new Thread(s => LoopReadSocket((int)s!)) + { + IsBackground = true, + Name = ".NET Net Address Monitor" + }.UnsafeStart(newSocket); } private static void CloseSocket() diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeCancelMibChangeNotify.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeCancelMibChangeNotify.cs index 54043bd7703ad..67f40c4c1a989 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeCancelMibChangeNotify.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeCancelMibChangeNotify.cs @@ -9,7 +9,7 @@ namespace System.Net.NetworkInformation // CancelMibChangeNotify2 guarantees that after it returns, the callback will NEVER be called. It may block // for a small amount of time if the callback is currently in progress, which is fine (and, intentional). - internal class SafeCancelMibChangeNotify : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeCancelMibChangeNotify : SafeHandleZeroOrMinusOneIsInvalid { public SafeCancelMibChangeNotify() : base(true) { } diff --git a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeFreeMibTable.cs b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeFreeMibTable.cs index fb52e503153d0..8eea228d82e47 100644 --- a/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeFreeMibTable.cs +++ b/src/libraries/System.Net.NetworkInformation/src/System/Net/NetworkInformation/SafeFreeMibTable.cs @@ -5,7 +5,7 @@ namespace System.Net.NetworkInformation { - internal class SafeFreeMibTable : SafeHandleZeroOrMinusOneIsInvalid + internal sealed class SafeFreeMibTable : SafeHandleZeroOrMinusOneIsInvalid { public SafeFreeMibTable() : base(true) { } diff --git a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs index 635513e668745..c784dd7353ae3 100644 --- a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs +++ b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs @@ -208,6 +208,7 @@ public static partial class HttpVersion public static readonly System.Version Version10; public static readonly System.Version Version11; public static readonly System.Version Version20; + public static readonly System.Version Version30; } public partial interface ICredentials { diff --git a/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs b/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs index 55a2b50ad2558..c3a87b5505530 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/HttpVersion.cs @@ -5,9 +5,15 @@ namespace System.Net { public static class HttpVersion { + /// Defines a instance that indicates an unknown version of HTTP. public static readonly Version Unknown = new Version(0, 0); + /// Defines a instance for HTTP 1.0. public static readonly Version Version10 = new Version(1, 0); + /// Defines a instance for HTTP 1.1. public static readonly Version Version11 = new Version(1, 1); + /// Defines a instance for HTTP 2.0. public static readonly Version Version20 = new Version(2, 0); + /// Defines a instance for HTTP 3.0. + public static readonly Version Version30 = new Version(3, 0); } } diff --git a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs index e1fadb3116d6f..813dc385f8151 100644 --- a/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs +++ b/src/libraries/System.Net.Requests/src/System/Net/HttpWebResponse.cs @@ -384,30 +384,6 @@ private void CheckDisposed() } } - private string GetHeaderValueAsString(IEnumerable values) - { - // There is always at least one value even if it is an empty string. - var enumerator = values.GetEnumerator(); - bool success = enumerator.MoveNext(); - Debug.Assert(success, "There should be at least one value"); - - string headerValue = enumerator.Current; - - if (enumerator.MoveNext()) - { - // Multi-valued header - var buffer = new StringBuilder(headerValue); - - do - { - buffer.Append(", "); - buffer.Append(enumerator.Current); - } while (enumerator.MoveNext()); - - return buffer.ToString(); - } - - return headerValue; - } + private string GetHeaderValueAsString(IEnumerable values) => string.Join(", ", values); } } diff --git a/src/libraries/System.Net.Requests/tests/HttpWebResponseHeaderTest.cs b/src/libraries/System.Net.Requests/tests/HttpWebResponseHeaderTest.cs index 6a1ea2eb3b7cc..deec37cece0ed 100644 --- a/src/libraries/System.Net.Requests/tests/HttpWebResponseHeaderTest.cs +++ b/src/libraries/System.Net.Requests/tests/HttpWebResponseHeaderTest.cs @@ -18,7 +18,6 @@ private static void HttpContinueMethod(int StatusCode, WebHeaderCollection httpH { } - [OuterLoop] [Fact] public async Task HttpWebRequest_ContinueDelegateProperty_Success() { @@ -34,7 +33,6 @@ await LoopbackServer.CreateServerAsync(async (server, url) => }); } - [OuterLoop] [Fact] public async Task HttpHeader_Set_Success() { @@ -48,19 +46,29 @@ await LoopbackServer.CreateServerAsync(async (server, url) => using (WebResponse response = await getResponse) { HttpWebResponse httpResponse = (HttpWebResponse)response; + Assert.Equal("UTF-8", httpResponse.CharacterSet); + Assert.Equal("", httpResponse.ContentEncoding); + Assert.Equal(5, httpResponse.ContentLength); + Assert.Equal(5, int.Parse(httpResponse.GetResponseHeader("Content-Length"))); + Assert.Equal("application/json; charset=UTF-8", httpResponse.ContentType); + Assert.False(httpResponse.IsFromCache); + Assert.False(httpResponse.IsMutuallyAuthenticated); + Assert.Equal("GET", httpResponse.Method); + Assert.Equal(HttpVersion.Version11, httpResponse.ProtocolVersion); + Assert.Equal(url, httpResponse.ResponseUri); + Assert.Equal("", httpResponse.Server); Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode); Assert.Equal("OK", httpResponse.StatusDescription); + Assert.True(httpResponse.SupportsHeaders); + CookieCollection cookieCollection = new CookieCollection(); httpResponse.Cookies = cookieCollection; Assert.Equal(cookieCollection, httpResponse.Cookies); - Assert.Equal(5,httpResponse.ContentLength); - Assert.Equal(5, int.Parse(httpResponse.GetResponseHeader("Content-Length"))); } }); } - [OuterLoop] [Fact] public async Task HttpWebResponse_Close_Success() { diff --git a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs index 5a5ccf63eb652..5333105b924aa 100644 --- a/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs +++ b/src/libraries/System.Net.Sockets/ref/System.Net.Sockets.cs @@ -220,6 +220,7 @@ public enum ProtocolType } public sealed partial class SafeSocketHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid { + public SafeSocketHandle() : base (default(bool)) { } public SafeSocketHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { } protected override bool ReleaseHandle() { throw null; } } @@ -406,6 +407,7 @@ public static void Select(System.Collections.IList? checkRead, System.Collection public bool SendAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; } public void SendFile(string? fileName) { } public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } + public void SendFile(string? fileName, System.ReadOnlySpan preBuffer, System.ReadOnlySpan postBuffer, System.Net.Sockets.TransmitFileOptions flags) { } public bool SendPacketsAsync(System.Net.Sockets.SocketAsyncEventArgs e) { throw null; } public int SendTo(byte[] buffer, int offset, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } public int SendTo(byte[] buffer, int size, System.Net.Sockets.SocketFlags socketFlags, System.Net.EndPoint remoteEP) { throw null; } diff --git a/src/libraries/System.Net.Sockets/src/ILLink/ILLinkTrim_LibraryBuild.xml b/src/libraries/System.Net.Sockets/src/ILLink/ILLinkTrim_LibraryBuild.xml deleted file mode 100644 index f135e9f7685c1..0000000000000 --- a/src/libraries/System.Net.Sockets/src/ILLink/ILLinkTrim_LibraryBuild.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs index 9d6311e19898a..bce056e31e2bb 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SafeSocketHandle.cs @@ -38,7 +38,7 @@ public SafeSocketHandle(IntPtr preexistingHandle, bool ownsHandle) SetHandleAndValid(preexistingHandle); } - private SafeSocketHandle() : base(ownsHandle: true) => OwnsHandle = true; + public SafeSocketHandle() : base(ownsHandle: true) => OwnsHandle = true; internal bool OwnsHandle { get; } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs index fd9a6c80fcf41..a9e08bd076c96 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Unix.cs @@ -195,7 +195,7 @@ private static void CheckTransmitFileOptions(TransmitFileOptions flags) } } - private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + private void SendFileInternal(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { CheckTransmitFileOptions(flags); @@ -208,7 +208,7 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB { // Send the preBuffer, if any // This will throw on error - if (preBuffer != null && preBuffer.Length > 0) + if (!preBuffer.IsEmpty) { Send(preBuffer); } @@ -230,7 +230,7 @@ private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postB // Send the postBuffer, if any // This will throw on error - if (postBuffer != null && postBuffer.Length > 0) + if (!postBuffer.IsEmpty) { Send(postBuffer); } diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs index 7fa5853e57902..267bfb0ab0aa5 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Windows.cs @@ -392,7 +392,7 @@ private Socket GetOrCreateAcceptSocket(Socket? acceptSocket, bool checkDisconnec return acceptSocket; } - private void SendFileInternal(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + private void SendFileInternal(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { // Open the file, if any FileStream? fileStream = OpenFile(fileName); diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index c3a94d426a503..efb20cb4166f1 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -417,22 +417,15 @@ public bool Blocking } } + // On .NET Framework, this property functions as a socket-level switch between IOCP-based and Win32 event based async IO. + // On that platform, setting UseOnlyOverlappedIO = true prevents assigning a completion port to the socket, + // allowing calls to DuplicateAndClose() even after performing asynchronous IO. + // .NET (Core) Windows sockets are entirely IOCP-based, and the concept of "overlapped IO" + // does not exist on other platforms, therefore UseOnlyOverlappedIO is a dummy, compat-only property. public bool UseOnlyOverlappedIO { - get - { - return false; - } - set - { - // - // This implementation does not support non-IOCP-based async I/O on Windows, and this concept is - // not even meaningful on other platforms. This option is really only functionally meaningful - // if the user calls DuplicateAndClose. Since we also don't support DuplicateAndClose, - // we can safely ignore the caller's choice here, rather than breaking compat further with something - // like PlatformNotSupportedException. - // - } + get { return false; } + set { } } // Gets the connection state of the Socket. This property will return the latest @@ -1269,10 +1262,57 @@ public int Send(ReadOnlySpan buffer, SocketFlags socketFlags, out SocketEr public void SendFile(string? fileName) { - SendFile(fileName, null, null, TransmitFileOptions.UseDefaultWorkerThread); + SendFile(fileName, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread); } + /// + /// Sends the file and buffers of data to a connected object + /// using the specified value. + /// + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// + /// + /// A array that contains data to be sent before the file is sent. This parameter can be . + /// + /// + /// A array that contains data to be sent after the file is sent. This parameter can be . + /// + /// + /// One or more of values. + /// + /// The object has been closed. + /// The object is not connected to a remote host. + /// The object is not in blocking mode and cannot accept this synchronous call. + /// The file was not found. + /// An error occurred when attempting to access the socket. public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, TransmitFileOptions flags) + { + SendFile(fileName, preBuffer.AsSpan(), postBuffer.AsSpan(), flags); + } + + /// + /// Sends the file and buffers of data to a connected object + /// using the specified value. + /// + /// + /// A that contains the path and name of the file to be sent. This parameter can be . + /// + /// + /// A that contains data to be sent before the file is sent. This buffer can be empty. + /// + /// + /// A that contains data to be sent after the file is sent. This buffer can be empty. + /// + /// + /// One or more of values. + /// + /// The object has been closed. + /// The object is not connected to a remote host. + /// The object is not in blocking mode and cannot accept this synchronous call. + /// The file was not found. + /// An error occurred when attempting to access the socket. + public void SendFile(string? fileName, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { ThrowIfDisposed(); @@ -1286,7 +1326,6 @@ public void SendFile(string? fileName, byte[]? preBuffer, byte[]? postBuffer, Tr if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"::SendFile() SRC:{LocalEndPoint} DST:{RemoteEndPoint} fileName:{fileName}"); SendFileInternal(fileName, preBuffer, postBuffer, flags); - } // Sends data to a specific end point, starting at the indicated location in the buffer. diff --git a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index a48eb9bbdaf0a..ab701286e435c 100644 --- a/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -291,13 +291,13 @@ public static unsafe SocketError Send(SafeSocketHandle handle, ReadOnlySpan preBuffer, ReadOnlySpan postBuffer, TransmitFileOptions flags) { fixed (byte* prePinnedBuffer = preBuffer) fixed (byte* postPinnedBuffer = postBuffer) { - bool success = TransmitFileHelper(handle, fileHandle, null, preBuffer, postBuffer, flags); - return (success ? SocketError.Success : GetLastSocketError()); + bool success = TransmitFileHelper(handle, fileHandle, null, (IntPtr)prePinnedBuffer, preBuffer.Length, (IntPtr)postPinnedBuffer, postBuffer.Length, flags); + return success ? SocketError.Success : GetLastSocketError(); } } @@ -1024,25 +1024,27 @@ private static unsafe bool TransmitFileHelper( SafeHandle socket, SafeHandle? fileHandle, NativeOverlapped* overlapped, - byte[]? preBuffer, - byte[]? postBuffer, + IntPtr pinnedPreBuffer, + int preBufferLength, + IntPtr pinnedPostBuffer, + int postBufferLength, TransmitFileOptions flags) { bool needTransmitFileBuffers = false; - Interop.Mswsock.TransmitFileBuffers transmitFileBuffers = default(Interop.Mswsock.TransmitFileBuffers); + Interop.Mswsock.TransmitFileBuffers transmitFileBuffers = default; - if (preBuffer != null && preBuffer.Length > 0) + if (preBufferLength > 0) { needTransmitFileBuffers = true; - transmitFileBuffers.Head = Marshal.UnsafeAddrOfPinnedArrayElement(preBuffer, 0); - transmitFileBuffers.HeadLength = preBuffer.Length; + transmitFileBuffers.Head = pinnedPreBuffer; + transmitFileBuffers.HeadLength = preBufferLength; } - if (postBuffer != null && postBuffer.Length > 0) + if (postBufferLength > 0) { needTransmitFileBuffers = true; - transmitFileBuffers.Tail = Marshal.UnsafeAddrOfPinnedArrayElement(postBuffer, 0); - transmitFileBuffers.TailLength = postBuffer.Length; + transmitFileBuffers.Tail = pinnedPostBuffer; + transmitFileBuffers.TailLength = postBufferLength; } bool releaseRef = false; @@ -1077,8 +1079,10 @@ public static unsafe SocketError SendFileAsync(SafeSocketHandle handle, FileStre handle, fileStream?.SafeFileHandle, asyncResult.DangerousOverlappedPointer, // SafeHandle was just created in SetUnmanagedStructures - preBuffer, - postBuffer, + preBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(preBuffer, 0) : IntPtr.Zero, + preBuffer?.Length ?? 0, + postBuffer is not null ? Marshal.UnsafeAddrOfPinnedArrayElement(postBuffer, 0) : IntPtr.Zero, + postBuffer?.Length ?? 0, flags); return asyncResult.ProcessOverlappedResult(success, 0); diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs index 539ed40e867af..d138281212564 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/SendFile.cs @@ -79,6 +79,8 @@ public void Disposed_ThrowsException() { s.Dispose(); Assert.Throws(() => s.SendFile(null)); + Assert.Throws(() => s.SendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); + Assert.Throws(() => s.SendFile(null, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread)); Assert.Throws(() => s.BeginSendFile(null, null, null)); Assert.Throws(() => s.BeginSendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread, null, null)); Assert.Throws(() => s.EndSendFile(null)); @@ -100,11 +102,44 @@ public void NotConnected_ThrowsException() using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { Assert.Throws(() => s.SendFile(null)); + Assert.Throws(() => s.SendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread)); + Assert.Throws(() => s.SendFile(null, ReadOnlySpan.Empty, ReadOnlySpan.Empty, TransmitFileOptions.UseDefaultWorkerThread)); Assert.Throws(() => s.BeginSendFile(null, null, null)); Assert.Throws(() => s.BeginSendFile(null, null, null, TransmitFileOptions.UseDefaultWorkerThread, null, null)); } } + [ConditionalTheory] + [PlatformSpecific(TestPlatforms.Windows)] + [InlineData(true)] + [InlineData(false)] + public async Task UdpConnection_ThrowsException(bool useAsync) + { + // Create file to send + byte[] preBuffer; + byte[] postBuffer; + Fletcher32 sentChecksum; + string filename = CreateFileToSend(size: 1, sendPreAndPostBuffers: false, out preBuffer, out postBuffer, out sentChecksum); + + using var client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + using var listener = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + listener.BindToAnonymousPort(IPAddress.Loopback); + + client.Connect(listener.LocalEndPoint); + + if (useAsync) + { + await Assert.ThrowsAsync(() => Task.Factory.FromAsync(client.BeginSendFile, client.EndSendFile, filename, null)); + } + else + { + Assert.Throws(() => client.SendFile(filename)); + } + + // Clean up the file we created + File.Delete(filename); + } + [Theory] [InlineData(false, false, false)] [InlineData(false, false, true)] @@ -156,6 +191,39 @@ public async Task SendFile_NoFile_Succeeds(bool useAsync, bool usePreBuffer, boo Assert.Equal(0, client.Available); } + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void SendFileSpan_NoFile_Succeeds(bool usePreBuffer, bool usePostBuffer) + { + using var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + using var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + listener.BindToAnonymousPort(IPAddress.Loopback); + listener.Listen(1); + + client.Connect(listener.LocalEndPoint); + using Socket server = listener.Accept(); + + server.SendFile(null); + Assert.Equal(0, client.Available); + + byte[] preBuffer = usePreBuffer ? new byte[1] : null; + byte[] postBuffer = usePostBuffer ? new byte[1] : null; + int bytesExpected = (usePreBuffer ? 1 : 0) + (usePostBuffer ? 1 : 0); + + server.SendFile(null, preBuffer.AsSpan(), postBuffer.AsSpan(), TransmitFileOptions.UseDefaultWorkerThread); + + byte[] receiveBuffer = new byte[1]; + for (int i = 0; i < bytesExpected; i++) + { + Assert.Equal(1, client.Receive(receiveBuffer)); + } + + Assert.Equal(0, client.Available); + } + [ActiveIssue("https://github.com/dotnet/runtime/issues/42534", TestPlatforms.Windows)] [OuterLoop("Creates and sends a file several gigabytes long")] [Theory] diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs index d8f83af4134ad..27efa1fd31979 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid { - private SafeFileHandle() : this(ownsHandle: true) + public SafeFileHandle() : this(ownsHandle: true) { } diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index 209e6330d85a9..dfcce3cfc65ac 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -10,7 +10,7 @@ public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid { private bool? _isAsync; - private SafeFileHandle() : base(true) + public SafeFileHandle() : base(true) { _isAsync = null; } diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs index c08fe0b36f1a7..5bbd898ec31d5 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFindHandle.Windows.cs @@ -5,7 +5,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeFindHandle() : base(true) { } + public SafeFindHandle() : base(true) { } protected override bool ReleaseHandle() { diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs index 0677576910a45..2b71d8c362052 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs @@ -17,7 +17,7 @@ namespace Internal.Win32.SafeHandles #endif sealed partial class SafeRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid { - internal SafeRegistryHandle() : base(true) { } + public SafeRegistryHandle() : base(true) { } public SafeRegistryHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) { diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.Unix.cs new file mode 100644 index 0000000000000..aa6bdbefc1721 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.Unix.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading; + +namespace Microsoft.Win32.SafeHandles +{ + public sealed partial class SafeWaitHandle : SafeHandleZeroOrMinusOneIsInvalid + { + protected override bool ReleaseHandle() + { + WaitSubsystem.DeleteHandle(handle); + return true; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.cs index 967db4e882388..a4d4735921584 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeWaitHandle.cs @@ -7,8 +7,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeWaitHandle : SafeHandleZeroOrMinusOneIsInvalid { - // Called by P/Invoke marshaler - private SafeWaitHandle() : base(true) + public SafeWaitHandle() : base(true) { } diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 8eacc114f8ca8..8f1b7359b99be 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -3592,6 +3592,9 @@ Attempt to update previously set global instance. + + Supplying a non-null inner should also be marked as Aggregated. + Length of items must be same as length of keys. 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 642ff0d671a92..fd5b33c24c7b1 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 @@ -14,6 +14,11 @@ true true $(MSBuildThisFileDirectory)ILLink\ + true + + + $(DefineConstants);TARGET_32BIT + $(DefineConstants);TARGET_64BIT $(DefineConstants);TARGET_UNIX @@ -29,8 +34,8 @@ - - + + @@ -483,6 +488,10 @@ + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs index 2f3ad1a09374a..28b279cd044a5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs @@ -103,6 +103,8 @@ public static partial class Activator Justification = "Implementation detail of Activator that linker intrinsically recognizes")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072:UnrecognizedReflectionPattern", Justification = "Implementation detail of Activator that linker intrinsically recognizes")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2096:UnrecognizedReflectionPattern", + Justification = "Implementation detail of Activator that linker intrinsically recognizes")] private static ObjectHandle? CreateInstanceInternal(string assemblyString, string typeName, bool ignoreCase, diff --git a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs index f013de2547532..9f22b48fde707 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Delegate.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Delegate.cs @@ -10,8 +10,6 @@ namespace System { public abstract partial class Delegate : ICloneable, ISerializable { - private protected const DynamicallyAccessedMemberTypes AllMethods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; - public virtual object Clone() => MemberwiseClone(); [return: NotNullIfNotNull("a")] @@ -49,10 +47,9 @@ public abstract partial class Delegate : ICloneable, ISerializable public static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase) => CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure: true)!; // V1 api: Creates open delegates to static methods only, relaxed signature checking disallowed. - public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(AllMethods)] Type target, string method) => CreateDelegate(type, target, method, ignoreCase: false, throwOnBindFailure: true)!; - public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(AllMethods)] Type target, string method, bool ignoreCase) => CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure: true)!; + public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method) => CreateDelegate(type, target, method, ignoreCase: false, throwOnBindFailure: true)!; + public static Delegate CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method, bool ignoreCase) => CreateDelegate(type, target, method, ignoreCase, throwOnBindFailure: true)!; -#if !CORERT protected virtual Delegate CombineImpl(Delegate? d) => throw new MulticastNotSupportedException(SR.Multicast_Combine); protected virtual Delegate? RemoveImpl(Delegate d) => d.Equals(this) ? null : this; @@ -63,7 +60,6 @@ public abstract partial class Delegate : ICloneable, ISerializable { return DynamicInvokeImpl(args); } -#endif public virtual void GetObjectData(SerializationInfo info, StreamingContext context) => throw new PlatformNotSupportedException(); 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 1e67a98ed310f..c6913ba2e5208 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 @@ -2748,6 +2748,32 @@ private unsafe void SendManifest(byte[]? rawManifest) #endif // FEATURE_MANAGED_ETW } + // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context. + // When that is the case, we have to build the custom assemblies on a member by hand. + internal static bool IsCustomAttributeDefinedHelper( + MemberInfo member, + Type attributeType, + EventManifestOptions flags = EventManifestOptions.None) + { + // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or + // System.Diagnostics.Tracing EventSource to be considered valid. This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package). + if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0) + { + // Let the runtime do the work for us, since we can execute code in this context. + return member.IsDefined(attributeType, inherit: false); + } + + foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member)) + { + if (AttributeTypeNamesMatch(attributeType, data.Constructor.ReflectedType!)) + { + return true; + } + } + + return false; + } + // Helper to deal with the fact that the type we are reflecting over might be loaded in the ReflectionOnly context. // When that is the case, we have the build the custom assemblies on a member by hand. internal static Attribute? GetCustomAttributeHelper( @@ -2758,18 +2784,13 @@ private unsafe void SendManifest(byte[]? rawManifest) Type attributeType, EventManifestOptions flags = EventManifestOptions.None) { + Debug.Assert(attributeType == typeof(EventAttribute) || attributeType == typeof(EventSourceAttribute)); // AllowEventSourceOverride is an option that allows either Microsoft.Diagnostics.Tracing or // System.Diagnostics.Tracing EventSource to be considered valid. This should not mattter anywhere but in Microsoft.Diagnostics.Tracing (nuget package). if (!member.Module.Assembly.ReflectionOnly && (flags & EventManifestOptions.AllowEventSourceOverride) == 0) { - // Let the runtime to the work for us, since we can execute code in this context. - Attribute? firstAttribute = null; - foreach (object attribute in member.GetCustomAttributes(attributeType, false)) - { - firstAttribute = (Attribute)attribute; - break; - } - return firstAttribute; + // Let the runtime do the work for us, since we can execute code in this context. + return member.GetCustomAttribute(attributeType, inherit: false); } foreach (CustomAttributeData data in CustomAttributeData.GetCustomAttributes(member)) @@ -3019,7 +3040,7 @@ private static bool AttributeTypeNamesMatch(Type attributeType, Type reflectedAt } // If we explicitly mark the method as not being an event, then honor that. - if (GetCustomAttributeHelper(method, typeof(NonEventAttribute), flags) != null) + if (IsCustomAttributeDefinedHelper(method, typeof(NonEventAttribute), flags)) continue; defaultEventAttribute = new EventAttribute(eventId); @@ -5475,7 +5496,7 @@ static FieldInfo[] GetEnumFields(Type localEnumType) sb.AppendLine(" "); foreach (Type enumType in mapsTab.Values) { - bool isbitmap = EventSource.GetCustomAttributeHelper(enumType, typeof(FlagsAttribute), flags) != null; + bool isbitmap = EventSource.IsCustomAttributeDefinedHelper(enumType, typeof(FlagsAttribute), flags); string mapKind = isbitmap ? "bitMap" : "valueMap"; sb.Append(" <").Append(mapKind).Append(" name=\"").Append(enumType.Name).AppendLine("\">"); diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs index 93d8974fecd6b..32bfd577ce4d8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/BitOperations.cs @@ -215,6 +215,32 @@ ref MemoryMarshal.GetReference(Log2DeBruijn), (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); } + /// Returns the integer (ceiling) log of the specified value, base 2. + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int Log2Ceiling(uint value) + { + int result = Log2(value); + if (PopCount(value) != 1) + { + result++; + } + return result; + } + + /// Returns the integer (ceiling) log of the specified value, base 2. + /// The value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int Log2Ceiling(ulong value) + { + int result = Log2(value); + if (PopCount(value) != 1) + { + result++; + } + return result; + } + /// /// Returns the population count (number of bits set) of a mask. /// Similar in behavior to the x86 instruction POPCNT. diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.ImplBase.cs b/src/libraries/System.Private.CoreLib/src/System/Random.ImplBase.cs new file mode 100644 index 0000000000000..055595cb84ac1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Random.ImplBase.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. + +namespace System +{ + public partial class Random + { + /// Base type for all generator implementations that plug into the base Random. + internal abstract class ImplBase + { + public abstract double Sample(); + + public abstract int Next(); + + public abstract int Next(int maxValue); + + public abstract int Next(int minValue, int maxValue); + + public abstract long NextInt64(); + + public abstract long NextInt64(long maxValue); + + public abstract long NextInt64(long minValue, long maxValue); + + public abstract float NextSingle(); + + public abstract double NextDouble(); + + public abstract void NextBytes(byte[] buffer); + + public abstract void NextBytes(Span buffer); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.LegacyImpl.cs b/src/libraries/System.Private.CoreLib/src/System/Random.LegacyImpl.cs new file mode 100644 index 0000000000000..eea6e4c9f3ff9 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Random.LegacyImpl.cs @@ -0,0 +1,224 @@ +// 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; + +namespace System +{ + public partial class Random + { + /// + /// Provides an implementation used for compatibility with cases where either a) the + /// sequence of numbers could be predicted based on the algorithm employed historically and + /// thus expected (e.g. a specific seed used in tests) or b) where a derived type may + /// reasonably expect its overrides to be called. The algorithm is based on a modified version + /// of Knuth's subtractive random number generator algorithm. See https://github.com/dotnet/runtime/issues/23198 + /// for a discussion of some of the modifications / discrepancies. + /// + private sealed class LegacyImpl : ImplBase + { + /// Thread-static instance used to seed any legacy implementations created with the default ctor. + [ThreadStatic] + private static XoshiroImpl? t_seedGenerator; + + /// Reference to the containing this implementation instance. + /// Used to ensure that any calls to other virtual members are performed using the Random-derived instance, if one exists. + private readonly Random _parent; + private readonly int[] _seedArray; + private int _inext; + private int _inextp; + + public LegacyImpl(Random parent) : this(parent, (t_seedGenerator ??= new()).Next()) + { + } + + public LegacyImpl(Random parent, int Seed) + { + _parent = parent; + + // Initialize seed array. + int[] seedArray = _seedArray = new int[56]; + + int subtraction = (Seed == int.MinValue) ? int.MaxValue : Math.Abs(Seed); + int mj = 161803398 - subtraction; // magic number based on Phi (golden ratio) + seedArray[55] = mj; + int mk = 1; + + int ii = 0; + for (int i = 1; i < 55; i++) + { + // The range [1..55] is special (Knuth) and so we're wasting the 0'th position. + if ((ii += 21) >= 55) + { + ii -= 55; + } + + seedArray[ii] = mk; + mk = mj - mk; + if (mk < 0) + { + mk += int.MaxValue; + } + + mj = seedArray[ii]; + } + + for (int k = 1; k < 5; k++) + { + for (int i = 1; i < 56; i++) + { + int n = i + 30; + if (n >= 55) + { + n -= 55; + } + + seedArray[i] -= seedArray[1 + n]; + if (seedArray[i] < 0) + { + seedArray[i] += int.MaxValue; + } + } + } + + _inextp = 21; + } + + public override double Sample() => + // Including the division at the end gives us significantly improved random number distribution. + InternalSample() * (1.0 / int.MaxValue); + + public override int Next() => InternalSample(); + + public override int Next(int maxValue) => (int)(_parent.Sample() * maxValue); + + public override int Next(int minValue, int maxValue) + { + long range = (long)maxValue - minValue; + return range <= int.MaxValue ? + (int)(_parent.Sample() * range) + minValue : + (int)((long)(GetSampleForLargeRange() * range) + minValue); + } + + public override long NextInt64() + { + while (true) + { + // Get top 63 bits to get a value in the range [0, long.MaxValue], but try again + // if the value is actually long.MaxValue, as the method is defined to return a value + // in the range [0, long.MaxValue). + ulong result = NextUInt64() >> 1; + if (result != long.MaxValue) + { + return (long)result; + } + } + } + + public override long NextInt64(long maxValue) => NextInt64(0, maxValue); + + public override long NextInt64(long minValue, long maxValue) + { + ulong exclusiveRange = (ulong)(maxValue - minValue); + + if (exclusiveRange > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue - minValue + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling(exclusiveRange); + while (true) + { + ulong result = NextUInt64() >> (sizeof(long) * 8 - bits); + if (result < exclusiveRange) + { + return (long)result + minValue; + } + } + } + + Debug.Assert(minValue == maxValue || minValue + 1 == maxValue); + return minValue; + } + + /// Produces a value in the range [0, ulong.MaxValue]. + private unsafe ulong NextUInt64() + { + Span resultBytes = stackalloc byte[8]; + NextBytes(resultBytes); + return BitConverter.ToUInt64(resultBytes); + } + + public override double NextDouble() => _parent.Sample(); + + public override float NextSingle() => (float)_parent.Sample(); + + public override void NextBytes(byte[] buffer) + { + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)InternalSample(); + } + } + + public override void NextBytes(Span buffer) + { + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = (byte)_parent.Next(); + } + } + + private int InternalSample() + { + int locINext = _inext; + if (++locINext >= 56) + { + locINext = 1; + } + + int locINextp = _inextp; + if (++locINextp >= 56) + { + locINextp = 1; + } + + int[] seedArray = _seedArray; + int retVal = seedArray[locINext] - seedArray[locINextp]; + + if (retVal == int.MaxValue) + { + retVal--; + } + if (retVal < 0) + { + retVal += int.MaxValue; + } + + seedArray[locINext] = retVal; + _inext = locINext; + _inextp = locINextp; + + return retVal; + } + + private double GetSampleForLargeRange() + { + // The distribution of the double returned by Sample is not good enough for a large range. + // If we use Sample for a range [int.MinValue..int.MaxValue), we will end up getting even numbers only. + int result = InternalSample(); + + // We can't use addition here: the distribution will be bad if we do that. + if (InternalSample() % 2 == 0) // decide the sign based on second sample + { + result = -result; + } + + double d = result; + d += int.MaxValue - 1; // get a number in range [0..2*int.MaxValue-1) + d /= 2u * int.MaxValue - 1; + return d; + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro128StarStarImpl.cs b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro128StarStarImpl.cs new file mode 100644 index 0000000000000..6fbb0a8b143f1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro128StarStarImpl.cs @@ -0,0 +1,235 @@ +// 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 Internal.Runtime.CompilerServices; + +namespace System +{ + public partial class Random + { + /// + /// Provides an implementation of the xoshiro128** algorithm. This implementation is used + /// on 32-bit when no seed is specified and an instance of the base Random class is constructed. + /// As such, we are free to implement however we see fit, without back compat concerns around + /// the sequence of numbers generated or what methods call what other methods. + /// + internal sealed class XoshiroImpl : ImplBase + { + // NextUInt32 is based on the algorithm from http://prng.di.unimi.it/xoshiro128starstar.c: + // + // Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + // + // To the extent possible under law, the author has dedicated all copyright + // and related and neighboring rights to this software to the public domain + // worldwide. This software is distributed without any warranty. + // + // See . + + private uint _s0, _s1, _s2, _s3; + + public unsafe XoshiroImpl() + { + uint* ptr = stackalloc uint[4]; + do + { + Interop.GetRandomBytes((byte*)ptr, 4 * sizeof(uint)); + _s0 = ptr[0]; + _s1 = ptr[1]; + _s2 = ptr[2]; + _s3 = ptr[3]; + } + while ((_s0 | _s1 | _s2 | _s3) == 0); // at least one value must be non-zero + } + + /// Produces a value in the range [0, uint.MaxValue]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods + internal uint NextUInt32() + { + uint result = BitOperations.RotateLeft(_s1 * 5, 7) * 9; + uint t = _s1 << 9; + + _s2 ^= _s0; + _s3 ^= _s1; + _s1 ^= _s2; + _s0 ^= _s3; + + _s2 ^= t; + _s3 = BitOperations.RotateLeft(_s3, 11); + + return result; + } + + /// Produces a value in the range [0, ulong.MaxValue]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods + internal ulong NextUInt64() => (((ulong)NextUInt32()) << 32) | NextUInt32(); + + public override int Next() + { + while (true) + { + // Get top 31 bits to get a value in the range [0, int.MaxValue], but try again + // if the value is actually int.MaxValue, as the method is defined to return a value + // in the range [0, int.MaxValue). + uint result = NextUInt32() >> 1; + if (result != int.MaxValue) + { + return (int)result; + } + } + } + + public override int Next(int maxValue) + { + if (maxValue > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling((uint)maxValue); + while (true) + { + uint result = NextUInt32() >> (sizeof(uint) * 8 - bits); + if (result < (uint)maxValue) + { + return (int)result; + } + } + } + + Debug.Assert(maxValue == 0 || maxValue == 1); + return 0; + } + + public override int Next(int minValue, int maxValue) + { + uint exclusiveRange = (uint)(maxValue - minValue); + + if (exclusiveRange > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling(exclusiveRange); + while (true) + { + uint result = NextUInt32() >> (sizeof(uint) * 8 - bits); + if (result < exclusiveRange) + { + return (int)result + minValue; + } + } + } + + Debug.Assert(minValue == maxValue || minValue + 1 == maxValue); + return minValue; + } + + public override long NextInt64() + { + while (true) + { + // Get top 63 bits to get a value in the range [0, long.MaxValue], but try again + // if the value is actually long.MaxValue, as the method is defined to return a value + // in the range [0, long.MaxValue). + ulong result = NextUInt64() >> 1; + if (result != long.MaxValue) + { + return (long)result; + } + } + } + + public override long NextInt64(long maxValue) + { + if (maxValue <= int.MaxValue) + { + return Next((int)maxValue); + } + + if (maxValue > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling((ulong)maxValue); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < (ulong)maxValue) + { + return (long)result; + } + } + } + + Debug.Assert(maxValue == 0 || maxValue == 1); + return 0; + } + + public override long NextInt64(long minValue, long maxValue) + { + ulong exclusiveRange = (ulong)(maxValue - minValue); + + if (exclusiveRange <= int.MaxValue) + { + return Next((int)exclusiveRange) + minValue; + } + + if (exclusiveRange > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling(exclusiveRange); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < exclusiveRange) + { + return (long)result + minValue; + } + } + } + + Debug.Assert(minValue == maxValue || minValue + 1 == maxValue); + return minValue; + } + + public override void NextBytes(byte[] buffer) => NextBytes((Span)buffer); + + public override unsafe void NextBytes(Span buffer) + { + while (buffer.Length >= sizeof(uint)) + { + Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(buffer), NextUInt32()); + buffer = buffer.Slice(sizeof(uint)); + } + + if (!buffer.IsEmpty) + { + uint next = NextUInt32(); + byte* remainingBytes = (byte*)&next; + Debug.Assert(buffer.Length < sizeof(uint)); + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = remainingBytes[i]; + } + } + } + + public override double NextDouble() => + // See comment in Xoshiro256StarStarImpl. + (NextUInt64() >> 11) * (1.0 / (1ul << 53)); + + public override float NextSingle() => + // See comment in Xoshiro256StarStarImpl. + (NextUInt32() >> 8) * (1.0f / (1u << 24)); + + public override double Sample() + { + Debug.Fail("Not used or called for this implementation."); + throw new NotSupportedException(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs new file mode 100644 index 0000000000000..e1505cf964b5b --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro256StarStarImpl.cs @@ -0,0 +1,230 @@ +// 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 Internal.Runtime.CompilerServices; + +namespace System +{ + public partial class Random + { + /// + /// Provides an implementation of the xoshiro256** algorithm. This implementation is used + /// on 64-bit when no seed is specified and an instance of the base Random class is constructed. + /// As such, we are free to implement however we see fit, without back compat concerns around + /// the sequence of numbers generated or what methods call what other methods. + /// + internal sealed class XoshiroImpl : ImplBase + { + // NextUInt64 is based on the algorithm from http://prng.di.unimi.it/xoshiro256starstar.c: + // + // Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + // + // To the extent possible under law, the author has dedicated all copyright + // and related and neighboring rights to this software to the public domain + // worldwide. This software is distributed without any warranty. + // + // See . + + private ulong _s0, _s1, _s2, _s3; + + public unsafe XoshiroImpl() + { + ulong* ptr = stackalloc ulong[4]; + do + { + Interop.GetRandomBytes((byte*)ptr, 4 * sizeof(ulong)); + _s0 = ptr[0]; + _s1 = ptr[1]; + _s2 = ptr[2]; + _s3 = ptr[3]; + } + while ((_s0 | _s1 | _s2 | _s3) == 0); // at least one value must be non-zero + } + + /// Produces a value in the range [0, uint.MaxValue]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by very few call sites + internal uint NextUInt32() => (uint)(NextUInt64() >> 32); + + /// Produces a value in the range [0, ulong.MaxValue]. + [MethodImpl(MethodImplOptions.AggressiveInlining)] // small-ish hot path used by a handful of "next" methods + internal ulong NextUInt64() + { + ulong result = BitOperations.RotateLeft(_s1 * 5, 7) * 9; + ulong t = _s1 << 17; + + _s2 ^= _s0; + _s3 ^= _s1; + _s1 ^= _s2; + _s0 ^= _s3; + + _s2 ^= t; + _s3 = BitOperations.RotateLeft(_s3, 45); + + return result; + } + + public override int Next() + { + while (true) + { + // Get top 31 bits to get a value in the range [0, int.MaxValue], but try again + // if the value is actually int.MaxValue, as the method is defined to return a value + // in the range [0, int.MaxValue). + ulong result = NextUInt64() >> 33; + if (result != int.MaxValue) + { + return (int)result; + } + } + } + + public override int Next(int maxValue) + { + if (maxValue > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling((uint)maxValue); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < (uint)maxValue) + { + return (int)result; + } + } + } + + Debug.Assert(maxValue == 0 || maxValue == 1); + return 0; + } + + public override int Next(int minValue, int maxValue) + { + ulong exclusiveRange = (ulong)(maxValue - minValue); + + if (exclusiveRange > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling(exclusiveRange); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < exclusiveRange) + { + return (int)result + minValue; + } + } + } + + Debug.Assert(minValue == maxValue || minValue + 1 == maxValue); + return minValue; + } + + public override long NextInt64() + { + while (true) + { + // Get top 63 bits to get a value in the range [0, long.MaxValue], but try again + // if the value is actually long.MaxValue, as the method is defined to return a value + // in the range [0, long.MaxValue). + ulong result = NextUInt64() >> 1; + if (result != long.MaxValue) + { + return (long)result; + } + } + } + + public override long NextInt64(long maxValue) + { + if (maxValue > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling((ulong)maxValue); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < (ulong)maxValue) + { + return (long)result; + } + } + } + + Debug.Assert(maxValue == 0 || maxValue == 1); + return 0; + } + + public override long NextInt64(long minValue, long maxValue) + { + ulong exclusiveRange = (ulong)(maxValue - minValue); + + if (exclusiveRange > 1) + { + // Narrow down to the smallest range [0, 2^bits] that contains maxValue. + // Then repeatedly generate a value in that outer range until we get one within the inner range. + int bits = BitOperations.Log2Ceiling(exclusiveRange); + while (true) + { + ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits); + if (result < exclusiveRange) + { + return (long)result + minValue; + } + } + } + + Debug.Assert(minValue == maxValue || minValue + 1 == maxValue); + return minValue; + } + + public override void NextBytes(byte[] buffer) => NextBytes((Span)buffer); + + public override unsafe void NextBytes(Span buffer) + { + while (buffer.Length >= sizeof(ulong)) + { + Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(buffer), NextUInt64()); + buffer = buffer.Slice(sizeof(ulong)); + } + + if (!buffer.IsEmpty) + { + ulong next = NextUInt64(); + byte* remainingBytes = (byte*)&next; + Debug.Assert(buffer.Length < sizeof(ulong)); + for (int i = 0; i < buffer.Length; i++) + { + buffer[i] = remainingBytes[i]; + } + } + } + + public override double NextDouble() => + // As described in http://prng.di.unimi.it/: + // "A standard double (64-bit) floating-point number in IEEE floating point format has 52 bits of significand, + // plus an implicit bit at the left of the significand. Thus, the representation can actually store numbers with + // 53 significant binary digits. Because of this fact, in C99 a 64-bit unsigned integer x should be converted to + // a 64-bit double using the expression + // (x >> 11) * 0x1.0p-53" + (NextUInt64() >> 11) * (1.0 / (1ul << 53)); + + public override float NextSingle() => + // Same as above, but with 24 bits instead of 53. + (NextUInt64() >> 40) * (1.0f / (1u << 24)); + + public override double Sample() + { + Debug.Fail("Not used or called for this implementation."); + throw new NotSupportedException(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Random.cs b/src/libraries/System.Private.CoreLib/src/System/Random.cs index 1cf0396f7ba8a..daaa02e93a533 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Random.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Random.cs @@ -3,195 +3,150 @@ namespace System { - public class Random + /// + /// Represents a pseudo-random number generator, which is an algorithm that produces a sequence of numbers + /// that meet certain statistical requirements for randomness. + /// + public partial class Random { - private static readonly Random s_globalRandom = new Random(GenerateGlobalSeed()); - [ThreadStatic] - private static Random? t_threadRandom; - - private readonly int[] _seedArray = new int[56]; - private int _inext; - private int _inextp; - - public Random() : this(ThreadStaticRandom.Next()) - { - } - - public Random(int Seed) - { - // Initialize seed array. - - int subtraction = (Seed == int.MinValue) ? int.MaxValue : Math.Abs(Seed); - int mj = 161803398 - subtraction; // magic number based on Phi (golden ratio) - _seedArray[55] = mj; - int mk = 1; - - int ii = 0; - for (int i = 1; i < 55; i++) - { - // The range [1..55] is special (Knuth) and so we're wasting the 0'th position. - if ((ii += 21) >= 55) - { - ii -= 55; - } - - _seedArray[ii] = mk; - mk = mj - mk; - if (mk < 0) - { - mk += int.MaxValue; - } - - mj = _seedArray[ii]; - } - - for (int k = 1; k < 5; k++) - { - for (int i = 1; i < 56; i++) - { - int n = i + 30; - if (n >= 55) - { - n -= 55; - } - - _seedArray[i] -= _seedArray[1 + n]; - if (_seedArray[i] < 0) - { - _seedArray[i] += int.MaxValue; - } - } - } - - _inextp = 21; - } - - protected virtual double Sample() => - // Including the division at the end gives us significantly improved random number distribution. - InternalSample() * (1.0 / int.MaxValue); - - public virtual int Next() => InternalSample(); - + /// The underlying generator implementation. + /// + /// This is separated out so that different generators can be used based on how this Random instance is constructed. + /// If it's built from a seed, then we may need to ensure backwards compatibility for folks expecting consistent sequences + /// based on that seed. If the instance is actually derived from Random, then we need to ensure the derived type's + /// overrides are called anywhere they were being called previously. But if the instance is the base type and is constructed + /// with the default constructor, we have a lot of flexibility as to how to optimize the performance and quality of the generator. + /// + private readonly ImplBase _impl; + + /// Initializes a new instance of the class using a default seed value. + public Random() => + // With no seed specified, if this is the base type, we can implement this however we like. + // If it's a derived type, for compat we respect the previous implementation, so that overrides + // are called as they were previously. + _impl = GetType() == typeof(Random) ? new XoshiroImpl() : new LegacyImpl(this); + + /// Initializes a new instance of the Random class, using the specified seed value. + /// + /// A number used to calculate a starting value for the pseudo-random number sequence. If a negative number + /// is specified, the absolute value of the number is used. + /// + public Random(int Seed) => + // With a custom seed, for compat we respect the previous implementation so that the same sequence + // previously output continues to be output. + _impl = new LegacyImpl(this, Seed); + + /// Returns a non-negative random integer. + /// A 32-bit signed integer that is greater than or equal to 0 and less than . + public virtual int Next() => _impl.Next(); + + /// Returns a non-negative random integer that is less than the specified maximum. + /// The exclusive upper bound of the random number to be generated. must be greater than or equal to 0. + /// + /// A 32-bit signed integer that is greater than or equal to 0, and less than ; that is, the range of return values ordinarily + /// includes 0 but not . However, if equals 0, is returned. + /// + /// is less than 0. public virtual int Next(int maxValue) { if (maxValue < 0) { - throw new ArgumentOutOfRangeException(nameof(maxValue), SR.Format(SR.ArgumentOutOfRange_MustBePositive, nameof(maxValue))); + ThrowMaxValueMustBeNonNegative(); } - return (int)(Sample() * maxValue); + return _impl.Next(maxValue); } + /// Returns a random integer that is within a specified range. + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. must be greater than or equal to . + /// + /// A 32-bit signed integer greater than or equal to and less than ; that is, the range of return values includes + /// but not . If minValue equals , is returned. + /// + /// is greater than . public virtual int Next(int minValue, int maxValue) { if (minValue > maxValue) { - throw new ArgumentOutOfRangeException(nameof(minValue), SR.Format(SR.Argument_MinMaxValue, nameof(minValue), nameof(maxValue))); + ThrowMinMaxValueSwapped(); } - long range = (long)maxValue - minValue; - return range <= int.MaxValue ? - (int)(Sample() * range) + minValue : - (int)((long)(GetSampleForLargeRange() * range) + minValue); + return _impl.Next(minValue, maxValue); } - public virtual double NextDouble() => Sample(); - - public virtual void NextBytes(byte[] buffer) + /// Returns a non-negative random integer. + /// A 64-bit signed integer that is greater than or equal to 0 and less than . + public virtual long NextInt64() => _impl.NextInt64(); + + /// Returns a non-negative random integer that is less than the specified maximum. + /// The exclusive upper bound of the random number to be generated. must be greater than or equal to 0. + /// + /// A 64-bit signed integer that is greater than or equal to 0, and less than ; that is, the range of return values ordinarily + /// includes 0 but not . However, if equals 0, is returned. + /// + /// is less than 0. + public virtual long NextInt64(long maxValue) { - if (buffer is null) + if (maxValue < 0) { - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.buffer); + ThrowMaxValueMustBeNonNegative(); } - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)InternalSample(); - } + return _impl.NextInt64(maxValue); } - public virtual void NextBytes(Span buffer) + /// Returns a random integer that is within a specified range. + /// The inclusive lower bound of the random number returned. + /// The exclusive upper bound of the random number returned. must be greater than or equal to . + /// + /// A 64-bit signed integer greater than or equal to and less than ; that is, the range of return values includes + /// but not . If minValue equals , is returned. + /// + /// is greater than . + public virtual long NextInt64(long minValue, long maxValue) { - for (int i = 0; i < buffer.Length; i++) - { - buffer[i] = (byte)Next(); - } - } - - private int InternalSample() - { - int locINext = _inext; - if (++locINext >= 56) - { - locINext = 1; - } - - int locINextp = _inextp; - if (++locINextp >= 56) + if (minValue > maxValue) { - locINextp = 1; + ThrowMinMaxValueSwapped(); } - int retVal = _seedArray[locINext] - _seedArray[locINextp]; - - if (retVal == int.MaxValue) - { - retVal--; - } - if (retVal < 0) - { - retVal += int.MaxValue; - } + return _impl.NextInt64(minValue, maxValue); + } - _seedArray[locINext] = retVal; - _inext = locINext; - _inextp = locINextp; + /// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. + /// A single-precision floating point number that is greater than or equal to 0.0, and less than 1.0. + public virtual float NextSingle() => _impl.NextSingle(); - return retVal; - } + /// Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0. + /// A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0. + public virtual double NextDouble() => _impl.NextDouble(); - private static Random ThreadStaticRandom + /// Fills the elements of a specified array of bytes with random numbers. + /// The array to be filled with random numbers. + /// is null. + public virtual void NextBytes(byte[] buffer) { - get + if (buffer is null) { - return t_threadRandom ??= CreateThreadStaticRandom(); - - static Random CreateThreadStaticRandom() - { - int seed; - lock (s_globalRandom) - { - seed = s_globalRandom.Next(); - } - - return new Random(seed); - } + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.buffer); } - } - private static unsafe int GenerateGlobalSeed() - { - int result; - Interop.GetRandomBytes((byte*)&result, sizeof(int)); - return result; + _impl.NextBytes(buffer); } - private double GetSampleForLargeRange() - { - // The distribution of the double returned by Sample is not good enough for a large range. - // If we use Sample for a range [int.MinValue..int.MaxValue), we will end up getting even numbers only. - int result = InternalSample(); + /// Fills the elements of a specified span of bytes with random numbers. + /// The array to be filled with random numbers. + public virtual void NextBytes(Span buffer) => _impl.NextBytes(buffer); - // We can't use addition here: the distribution will be bad if we do that. - if (InternalSample() % 2 == 0) // decide the sign based on second sample - { - result = -result; - } + /// Returns a random floating-point number between 0.0 and 1.0. + /// A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0. + protected virtual double Sample() => _impl.Sample(); - double d = result; - d += int.MaxValue - 1; // get a number in range [0..2*int.MaxValue-1) - d /= 2u * int.MaxValue - 1; - return d; - } + private static void ThrowMaxValueMustBeNonNegative() => + throw new ArgumentOutOfRangeException("maxValue", SR.Format(SR.ArgumentOutOfRange_NeedNonNegNum, "maxValue")); + + private static void ThrowMinMaxValueSwapped() => + throw new ArgumentOutOfRangeException("minValue", SR.Format(SR.Argument_MinMaxValue, "minValue", "maxValue")); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/StackBehaviour.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/StackBehaviour.cs index b85bd77393faf..16035836ae63d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/StackBehaviour.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/Emit/StackBehaviour.cs @@ -8,7 +8,7 @@ ** Purpose: Exposes StackBehaviour Attribute of IL. ** ** THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT BY HAND! -** See $(RepoRoot)\src\inc\OpCodeGen.pl for more information.** +** See $(RepoRoot)\src\coreclr\inc\OpCodeGen.pl for more information.** ==============================================================*/ namespace System.Reflection.Emit diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs index 3a622258b6e30..9dc7e1789ebad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs @@ -20,6 +20,8 @@ public enum CreateObjectFlags None = 0, TrackerObject = 1, UniqueInstance = 2, + Aggregation = 4, + Unwrap = 8, } [SupportedOSPlatform("windows")] @@ -61,6 +63,11 @@ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, Create throw new PlatformNotSupportedException(); } + public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper, IntPtr inner) + { + throw new PlatformNotSupportedException(); + } + protected abstract void ReleaseObjects(IEnumerable objects); public static void RegisterForTrackerSupport(ComWrappers instance) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs index 41a0645bd1292..9f8cc4cd04b8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.cs @@ -996,5 +996,16 @@ internal static unsafe uint SysStringByteLen(IntPtr s) [SupportedOSPlatform("windows")] public static Type? GetTypeFromCLSID(Guid clsid) => GetTypeFromCLSID(clsid, null, throwOnError: false); + + /// + /// Initializes the underlying handle of a newly created to the provided value. + /// + /// instance to update + /// Pre-existing handle + public static void InitHandle(SafeHandle safeHandle, IntPtr handle) + { + // To help maximize performance of P/Invokes, don't check if safeHandle is null. + safeHandle.SetHandle(handle); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs index 70376d41563ff..78053e4462175 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs @@ -74,7 +74,7 @@ protected SafeHandle(IntPtr invalidHandleValue, bool ownsHandle) } #endif - protected void SetHandle(IntPtr handle) => this.handle = handle; + protected internal void SetHandle(IntPtr handle) => this.handle = handle; public IntPtr DangerousGetHandle() => handle; 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 18056b147b2ee..7ba50af0e642f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Byte.cs @@ -1893,14 +1893,18 @@ private static int LocateLastFoundByte(Vector match) var vector64 = Vector.AsVectorUInt64(match); ulong candidate = 0; int i = Vector.Count - 1; - // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001 - for (; i >= 0; i--) + + // 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) 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 4c4070ef82e00..09b56d51abbdc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.Char.cs @@ -1615,14 +1615,18 @@ private static int LocateLastFoundChar(Vector match) var vector64 = Vector.AsVectorUInt64(match); ulong candidate = 0; int i = Vector.Count - 1; - // Pattern unrolled by jit https://github.com/dotnet/coreclr/pull/8001 - for (; i >= 0; i--) + + // 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) diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs new file mode 100644 index 0000000000000..2cdaeb6bb55c7 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/EventWaitHandle.Unix.cs @@ -0,0 +1,83 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + public partial class EventWaitHandle + { + private void CreateEventCore(bool initialState, EventResetMode mode, string name, out bool createdNew) + { + if (name != null) + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + + SafeWaitHandle = WaitSubsystem.NewEvent(initialState, mode); + createdNew = true; + } + + private static OpenExistingResult OpenExistingWorker(string name, out EventWaitHandle result) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + } + + public bool Reset() + { + SafeWaitHandle waitHandle = ValidateHandle(); + try + { + WaitSubsystem.ResetEvent(waitHandle.DangerousGetHandle()); + return true; + } + finally + { + waitHandle.DangerousRelease(); + } + } + + public bool Set() + { + SafeWaitHandle waitHandle = ValidateHandle(); + try + { + WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle()); + return true; + } + finally + { + waitHandle.DangerousRelease(); + } + } + + internal static bool Set(SafeWaitHandle waitHandle) + { + waitHandle.DangerousAddRef(); + try + { + WaitSubsystem.SetEvent(waitHandle.DangerousGetHandle()); + return true; + } + finally + { + waitHandle.DangerousRelease(); + } + } + + private SafeWaitHandle ValidateHandle() + { + // The field value is modifiable via the public property, save it locally + // to ensure that one instance is used in all places in this method + SafeWaitHandle waitHandle = SafeWaitHandle; + if (waitHandle.IsInvalid) + { + ThrowInvalidHandleException(); + } + + waitHandle.DangerousAddRef(); + return waitHandle; + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs index 908182b9d1e0b..7058e5f13364c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Interlocked.cs @@ -69,6 +69,21 @@ public static uint Exchange(ref uint location1, uint value) => [CLSCompliant(false)] public static ulong Exchange(ref ulong location1, ulong value) => (ulong)Exchange(ref Unsafe.As(ref location1), (long)value); + + /// Sets a platform-specific handle or pointer to a specified value and returns the original value, as an atomic operation. + /// The variable to set to the specified value. + /// The value to which the parameter is set. + /// The original value of . + /// The address of location1 is a null pointer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr Exchange(ref IntPtr location1, IntPtr value) + { +#if TARGET_64BIT + return (IntPtr)Interlocked.Exchange(ref Unsafe.As(ref location1), (long)value); +#else + return (IntPtr)Interlocked.Exchange(ref Unsafe.As(ref location1), (int)value); +#endif + } #endregion #region CompareExchange @@ -93,6 +108,22 @@ public static uint CompareExchange(ref uint location1, uint value, uint comparan [CLSCompliant(false)] public static ulong CompareExchange(ref ulong location1, ulong value, ulong comparand) => (ulong)CompareExchange(ref Unsafe.As(ref location1), (long)value, (long)comparand); + + /// Compares two platform-specific handles or pointers for equality and, if they are equal, replaces the first one. + /// The destination , whose value is compared with the value of and possibly replaced by . + /// The that replaces the destination value if the comparison results in equality. + /// The that is compared to the value at . + /// The original value in . + /// The address of is a null pointer. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand) + { +#if TARGET_64BIT + return (IntPtr)Interlocked.CompareExchange(ref Unsafe.As(ref location1), (long)value, (long)comparand); +#else + return (IntPtr)Interlocked.CompareExchange(ref Unsafe.As(ref location1), (int)value, (int)comparand); +#endif + } #endregion #region Add diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs new file mode 100644 index 0000000000000..27f1d17091603 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Mutex.Unix.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + public sealed partial class Mutex + { + private void CreateMutexCore(bool initiallyOwned, string name, out bool createdNew) + { + if (name != null) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + } + + SafeWaitHandle = WaitSubsystem.NewMutex(initiallyOwned); + createdNew = true; + } + + private static OpenExistingResult OpenExistingWorker(string name, out Mutex result) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + } + + public void ReleaseMutex() + { + // The field value is modifiable via the public property, save it locally + // to ensure that one instance is used in all places in this method + SafeWaitHandle waitHandle = SafeWaitHandle; + if (waitHandle.IsInvalid) + { + ThrowInvalidHandleException(); + } + + waitHandle.DangerousAddRef(); + try + { + WaitSubsystem.ReleaseMutex(waitHandle.DangerousGetHandle()); + } + finally + { + waitHandle.DangerousRelease(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Unix.cs new file mode 100644 index 0000000000000..b2184b6af4ade --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/Semaphore.Unix.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + public sealed partial class Semaphore + { + private void CreateSemaphoreCore(int initialCount, int maximumCount, string name, out bool createdNew) + { + if (name != null) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + } + + SafeWaitHandle = WaitSubsystem.NewSemaphore(initialCount, maximumCount); + createdNew = true; + } + + private static OpenExistingResult OpenExistingWorker(string name, out Semaphore result) + { + throw new PlatformNotSupportedException(SR.PlatformNotSupported_NamedSynchronizationPrimitives); + } + + private int ReleaseCore(int releaseCount) + { + // The field value is modifiable via the public property, save it locally + // to ensure that one instance is used in all places in this method + SafeWaitHandle waitHandle = SafeWaitHandle; + if (waitHandle.IsInvalid) + { + ThrowInvalidHandleException(); + } + + waitHandle.DangerousAddRef(); + try + { + return WaitSubsystem.ReleaseSemaphore(waitHandle.DangerousGetHandle(), releaseCount); + } + finally + { + waitHandle.DangerousRelease(); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs index 0d5f2918807d0..cdd5aa000f0f7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/ThreadPool.cs @@ -549,7 +549,7 @@ internal static bool LocalFindAndPop(object callback) int c = queues.Length; Debug.Assert(c > 0, "There must at least be a queue for this thread."); int maxIndex = c - 1; - int i = tl.random.Next(c); + uint i = tl.random.NextUInt32() % (uint)c; while (c > 0) { i = (i < maxIndex) ? i + 1 : 0; @@ -799,31 +799,6 @@ private static void DispatchWorkItemWithWorkerTracking(object workItem, Thread c } } - // Simple random number generator. We don't need great randomness, we just need a little and for it to be fast. - internal struct FastRandom // xorshift prng - { - private uint _w, _x, _y, _z; - - public FastRandom(int seed) - { - _x = (uint)seed; - _w = 88675123; - _y = 362436069; - _z = 521288629; - } - - public int Next(int maxValue) - { - Debug.Assert(maxValue > 0); - - uint t = _x ^ (_x << 11); - _x = _y; _y = _z; _z = _w; - _w = _w ^ (_w >> 19) ^ (t ^ (t >> 8)); - - return (int)(_w % (uint)maxValue); - } - } - // Holds a WorkStealingQueue, and removes it from the list when this object is no longer referenced. internal sealed class ThreadPoolWorkQueueThreadLocals { @@ -834,7 +809,7 @@ internal sealed class ThreadPoolWorkQueueThreadLocals public readonly ThreadPoolWorkQueue.WorkStealingQueue workStealingQueue; public readonly Thread currentThread; public readonly object? threadLocalCompletionCountObject; - public FastRandom random = new FastRandom(Environment.CurrentManagedThreadId); // mutable struct, do not copy or make readonly + public readonly Random.XoshiroImpl random = new Random.XoshiroImpl(); public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.cs new file mode 100644 index 0000000000000..8a5346fa38632 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Unix.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 Microsoft.Win32.SafeHandles; + +namespace System.Threading +{ + public abstract partial class WaitHandle + { + private static int WaitOneCore(IntPtr handle, int millisecondsTimeout) => + WaitSubsystem.Wait(handle, millisecondsTimeout, true); + + internal static int WaitMultipleIgnoringSyncContext(Span handles, bool waitAll, int millisecondsTimeout) => + WaitSubsystem.Wait(handles, waitAll, millisecondsTimeout); + + private static int SignalAndWaitCore(IntPtr handleToSignal, IntPtr handleToWaitOn, int millisecondsTimeout) => + WaitSubsystem.SignalAndWait(handleToSignal, handleToWaitOn, millisecondsTimeout); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs new file mode 100644 index 0000000000000..dc922c465aa40 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.HandleManager.Unix.cs @@ -0,0 +1,56 @@ +// 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; +using System.Runtime.InteropServices; + +namespace System.Threading +{ + internal static partial class WaitSubsystem + { + private static class HandleManager + { + public static IntPtr NewHandle(WaitableObject waitableObject) + { + Debug.Assert(waitableObject != null); + + IntPtr handle = RuntimeImports.RhHandleAlloc(waitableObject, GCHandleType.Normal); + + // SafeWaitHandle treats -1 and 0 as invalid, and the handle should not be these values anyway + Debug.Assert(handle != IntPtr.Zero); + Debug.Assert(handle != new IntPtr(-1)); + return handle; + } + + public static WaitableObject FromHandle(IntPtr handle) + { + if (handle == IntPtr.Zero || handle == new IntPtr(-1)) + { + WaitHandle.ThrowInvalidHandleException(); + } + + // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by + // design, IntPtr is unsafe by nature. + return (WaitableObject)RuntimeImports.RhHandleGet(handle); + } + + /// + /// Unlike on Windows, a handle may not be deleted more than once with this implementation + /// + public static void DeleteHandle(IntPtr handle) + { + if (handle == IntPtr.Zero || handle == new IntPtr(-1)) + { + return; + } + + // We don't know if any other handles are invalid, and this may crash or otherwise do bad things, that is by + // design, IntPtr is unsafe by nature. + ((WaitableObject)RuntimeImports.RhHandleGet(handle)).OnDeleteHandle(); + RuntimeImports.RhHandleFree(handle); + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs new file mode 100644 index 0000000000000..92c67fcbfb676 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.ThreadWaitInfo.Unix.cs @@ -0,0 +1,734 @@ +// 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.Threading +{ + internal static partial class WaitSubsystem + { + /// + /// Contains thread-specific information for the wait subsystem. There is one instance per thread that is registered + /// using s with each that the thread is waiting upon. + /// + /// Used by the wait subsystem on Unix, so this class cannot have any dependencies on the wait subsystem. + /// + public sealed class ThreadWaitInfo + { + private readonly Thread _thread; + + /// + /// The monitor the thread would wait upon when the wait needs to be interruptible + /// + private LowLevelMonitor _waitMonitor; + + + /// + /// Thread wait state. The following members indicate the waiting state of the thread, and convery information from + /// a signaler to the waiter. They are synchronized with . + /// + private WaitSignalState _waitSignalState; + + /// + /// Index of the waitable object in , which got signaled and satisfied the wait. -1 if + /// the wait has not yet been satisfied. + /// + private int _waitedObjectIndexThatSatisfiedWait; + + /// + /// Information about the current wait, including the type of wait, the s involved in + /// the wait, etc. They are synchronized with . + /// + private bool _isWaitForAll; + + /// + /// Number of s the thread is waiting upon + /// + private int _waitedCount; + + /// + /// - s that are waited upon by the thread. This array is also used for temporarily + /// storing s corresponding to s when the thread is not + /// waiting. + /// - The filled count is + /// - Indexes in all arrays that use correspond + /// + private WaitableObject[] _waitedObjects; + + /// + /// - Nodes used for registering a thread's wait on each , in the + /// linked list + /// - The filled count is + /// - Indexes in all arrays that use correspond + /// + private WaitedListNode[] _waitedListNodes; + + /// + /// Indicates whether the next wait should be interrupted. + /// + /// Synchronization: + /// - In most cases, reads and writes are synchronized with + /// - Sleep(nonzero) intentionally does not acquire , but it must acquire + /// to do the wait. To support this case, a pending interrupt is recorded while + /// and are locked, and the read and reset for Sleep(nonzero) are + /// done while is locked. + /// - Sleep(0) intentionally does not acquire any lock, so it uses an interlocked compare-exchange for the read and + /// reset, see + /// + private int _isPendingInterrupt; + + //////////////////////////////////////////////////////////////// + + /// + /// Linked list of mutex s that are owned by the thread and need to be abandoned before + /// the thread exits. The linked list has only a head and no tail, which means acquired mutexes are prepended and + /// mutexes are abandoned in reverse order. + /// + private WaitableObject _lockedMutexesHead; + + public ThreadWaitInfo(Thread thread) + { + Debug.Assert(thread != null); + + _thread = thread; + _waitMonitor.Initialize(); + _waitSignalState = WaitSignalState.NotWaiting; + _waitedObjectIndexThatSatisfiedWait = -1; + + // Preallocate to make waiting for single handle fault-free + _waitedObjects = new WaitableObject[1]; + _waitedListNodes = new WaitedListNode[1] { new WaitedListNode(this, 0) }; + } + + ~ThreadWaitInfo() + { + _waitMonitor.Dispose(); + } + + public Thread Thread => _thread; + + private bool IsWaiting + { + get + { + _waitMonitor.VerifyIsLocked(); + return _waitSignalState < WaitSignalState.NotWaiting; + } + } + + /// + /// Callers must ensure to clear the array after use. Once is called (followed + /// by a call to , the array will be cleared automatically. + /// + public WaitableObject[] GetWaitedObjectArray(int requiredCapacity) + { + Debug.Assert(_thread == Thread.CurrentThread); + Debug.Assert(_waitedCount == 0); + +#if DEBUG + for (int i = 0; i < _waitedObjects.Length; ++i) + { + Debug.Assert(_waitedObjects[i] == null); + } +#endif + + int currentLength = _waitedObjects.Length; + if (currentLength < requiredCapacity) + _waitedObjects = new WaitableObject[Math.Max(requiredCapacity, + Math.Min(WaitHandle.MaxWaitHandles, 2 * currentLength))]; + + return _waitedObjects; + } + + private WaitedListNode[] GetWaitedListNodeArray(int requiredCapacity) + { + Debug.Assert(_thread == Thread.CurrentThread); + Debug.Assert(_waitedCount == 0); + + int currentLength = _waitedListNodes.Length; + if (currentLength < requiredCapacity) + { + WaitedListNode[] newItems = new WaitedListNode[Math.Max(requiredCapacity, + Math.Min(WaitHandle.MaxWaitHandles, 2 * currentLength))]; + + Array.Copy(_waitedListNodes, 0, newItems, 0, currentLength); + for (int i = currentLength; i < newItems.Length; i++) + newItems[i] = new WaitedListNode(this, i); + + _waitedListNodes = newItems; + } + + return _waitedListNodes; + } + + /// + /// The caller is expected to populate and pass in the number of objects filled + /// + public void RegisterWait(int waitedCount, bool prioritize, bool isWaitForAll) + { + s_lock.VerifyIsLocked(); + Debug.Assert(_thread == Thread.CurrentThread); + + Debug.Assert(waitedCount > (isWaitForAll ? 1 : 0)); + Debug.Assert(waitedCount <= _waitedObjects.Length); + + Debug.Assert(_waitedCount == 0); + + WaitableObject[] waitedObjects = _waitedObjects; +#if DEBUG + for (int i = 0; i < waitedCount; ++i) + { + Debug.Assert(waitedObjects[i] != null); + } + for (int i = waitedCount; i < waitedObjects.Length; ++i) + { + Debug.Assert(waitedObjects[i] == null); + } +#endif + + bool success = false; + WaitedListNode[] waitedListNodes; + try + { + waitedListNodes = GetWaitedListNodeArray(waitedCount); + success = true; + } + finally + { + if (!success) + { + // Once this function is called, the caller is effectively transferring ownership of the waited objects + // to this and the wait functions. On exception, clear the array. + for (int i = 0; i < waitedCount; ++i) + { + waitedObjects[i] = null; + } + } + } + + _isWaitForAll = isWaitForAll; + _waitedCount = waitedCount; + if (prioritize) + { + for (int i = 0; i < waitedCount; ++i) + { + waitedListNodes[i].RegisterPrioritizedWait(waitedObjects[i]); + } + } + else + { + for (int i = 0; i < waitedCount; ++i) + { + waitedListNodes[i].RegisterWait(waitedObjects[i]); + } + } + } + + public void UnregisterWait() + { + s_lock.VerifyIsLocked(); + Debug.Assert(_waitedCount > (_isWaitForAll ? 1 : 0)); + + for (int i = 0; i < _waitedCount; ++i) + { + _waitedListNodes[i].UnregisterWait(_waitedObjects[i]); + _waitedObjects[i] = null; + } + _waitedCount = 0; + } + + private int ProcessSignaledWaitState() + { + s_lock.VerifyIsNotLocked(); + _waitMonitor.VerifyIsLocked(); + Debug.Assert(_thread == Thread.CurrentThread); + + switch (_waitSignalState) + { + case WaitSignalState.Waiting: + case WaitSignalState.Waiting_Interruptible: + return WaitHandle.WaitTimeout; + + case WaitSignalState.NotWaiting_SignaledToSatisfyWait: + { + Debug.Assert(_waitedObjectIndexThatSatisfiedWait >= 0); + int waitedObjectIndexThatSatisfiedWait = _waitedObjectIndexThatSatisfiedWait; + _waitedObjectIndexThatSatisfiedWait = -1; + return waitedObjectIndexThatSatisfiedWait; + } + + case WaitSignalState.NotWaiting_SignaledToSatisfyWaitWithAbandonedMutex: + { + Debug.Assert(_waitedObjectIndexThatSatisfiedWait >= 0); + int waitedObjectIndexThatSatisfiedWait = _waitedObjectIndexThatSatisfiedWait; + _waitedObjectIndexThatSatisfiedWait = -1; + return WaitHandle.WaitAbandoned + waitedObjectIndexThatSatisfiedWait; + } + + case WaitSignalState.NotWaiting_SignaledToAbortWaitDueToMaximumMutexReacquireCount: + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + throw new OverflowException(SR.Overflow_MutexReacquireCount); + + default: + Debug.Assert(_waitSignalState == WaitSignalState.NotWaiting_SignaledToInterruptWait); + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + throw new ThreadInterruptedException(); + } + } + + public int Wait(int timeoutMilliseconds, bool interruptible, bool isSleep) + { + if (isSleep) + { + s_lock.VerifyIsNotLocked(); + } + else + { + s_lock.VerifyIsLocked(); + } + Debug.Assert(_thread == Thread.CurrentThread); + + Debug.Assert(timeoutMilliseconds >= -1); + Debug.Assert(timeoutMilliseconds != 0); // caller should have taken care of it + + _thread.SetWaitSleepJoinState(); + + // must be acquired before is released, to ensure that there is + // no gap during which a waited object may be signaled to satisfy the wait but the thread may not yet be in a + // wait state to accept the signal + _waitMonitor.Acquire(); + if (!isSleep) + { + s_lock.Release(); + } + + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + Debug.Assert(_waitSignalState == WaitSignalState.NotWaiting); + + // A signaled state may be set only when the thread is in one of the following states + _waitSignalState = interruptible ? WaitSignalState.Waiting_Interruptible : WaitSignalState.Waiting; + + try + { + if (isSleep && interruptible && CheckAndResetPendingInterrupt) + { + throw new ThreadInterruptedException(); + } + + if (timeoutMilliseconds < 0) + { + do + { + _waitMonitor.Wait(); + } while (IsWaiting); + + int waitResult = ProcessSignaledWaitState(); + Debug.Assert(waitResult != WaitHandle.WaitTimeout); + return waitResult; + } + + int elapsedMilliseconds = 0; + int startTimeMilliseconds = Environment.TickCount; + while (true) + { + bool monitorWaitResult = _waitMonitor.Wait(timeoutMilliseconds - elapsedMilliseconds); + + // It's possible for the wait to have timed out, but before the monitor could reacquire the lock, a + // signaler could have acquired it and signaled to satisfy the wait or interrupt the thread. Accept the + // signal and ignore the wait timeout. + int waitResult = ProcessSignaledWaitState(); + if (waitResult != WaitHandle.WaitTimeout) + { + return waitResult; + } + + if (monitorWaitResult) + { + elapsedMilliseconds = Environment.TickCount - startTimeMilliseconds; + if (elapsedMilliseconds < timeoutMilliseconds) + { + continue; + } + } + + // Timeout + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + break; + } + } + finally + { + _waitSignalState = WaitSignalState.NotWaiting; + _waitMonitor.Release(); + + _thread.ClearWaitSleepJoinState(); + } + + // Timeout. It's ok to read without acquiring here, because it + // is initially set by this thread, and another thread cannot unregister this thread's wait without first + // signaling this thread, in which case this thread wouldn't be timing out. + Debug.Assert(isSleep == (_waitedCount == 0)); + if (!isSleep) + { + s_lock.Acquire(); + UnregisterWait(); + s_lock.Release(); + } + return WaitHandle.WaitTimeout; + } + + public static void UninterruptibleSleep0() + { + s_lock.VerifyIsNotLocked(); + + // On Unix, a thread waits on a condition variable. The timeout time will have already elapsed at the time + // of the call. The documentation does not state whether the thread yields or does nothing before returning + // an error, and in some cases, suggests that doing nothing is acceptable. The behavior could also be + // different between distributions. Yield directly here. + Thread.Yield(); + } + + public static void Sleep(int timeoutMilliseconds, bool interruptible) + { + s_lock.VerifyIsNotLocked(); + Debug.Assert(timeoutMilliseconds >= -1); + + if (timeoutMilliseconds == 0) + { + if (interruptible && Thread.CurrentThread.WaitInfo.CheckAndResetPendingInterrupt_NotLocked) + { + throw new ThreadInterruptedException(); + } + + UninterruptibleSleep0(); + return; + } + + int waitResult = + Thread + .CurrentThread + .WaitInfo + .Wait(timeoutMilliseconds, interruptible, isSleep: true); + Debug.Assert(waitResult == WaitHandle.WaitTimeout); + } + + public bool TrySignalToSatisfyWait(WaitedListNode registeredListNode, bool isAbandonedMutex) + { + s_lock.VerifyIsLocked(); + Debug.Assert(_thread != Thread.CurrentThread); + + Debug.Assert(registeredListNode != null); + Debug.Assert(registeredListNode.WaitInfo == this); + Debug.Assert(registeredListNode.WaitedObjectIndex >= 0); + Debug.Assert(registeredListNode.WaitedObjectIndex < _waitedCount); + + Debug.Assert(_waitedCount > (_isWaitForAll ? 1 : 0)); + + int signaledWaitedObjectIndex = registeredListNode.WaitedObjectIndex; + bool isWaitForAll = _isWaitForAll; + bool wouldAnyMutexReacquireCountOverflow = false; + if (isWaitForAll) + { + // Determine if all waits would be satisfied + if (!WaitableObject.WouldWaitForAllBeSatisfiedOrAborted( + _thread, + _waitedObjects, + _waitedCount, + signaledWaitedObjectIndex, + ref wouldAnyMutexReacquireCountOverflow, + ref isAbandonedMutex)) + { + return false; + } + } + + // The wait would be satisfied. Before making changes to satisfy the wait, acquire the monitor and verify that + // the thread can accept a signal. + _waitMonitor.Acquire(); + + if (!IsWaiting) + { + _waitMonitor.Release(); + return false; + } + + if (isWaitForAll && !wouldAnyMutexReacquireCountOverflow) + { + // All waits would be satisfied, accept the signals + WaitableObject.SatisfyWaitForAll(this, _waitedObjects, _waitedCount, signaledWaitedObjectIndex); + } + + UnregisterWait(); + + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + if (wouldAnyMutexReacquireCountOverflow) + { + _waitSignalState = WaitSignalState.NotWaiting_SignaledToAbortWaitDueToMaximumMutexReacquireCount; + } + else + { + _waitedObjectIndexThatSatisfiedWait = signaledWaitedObjectIndex; + _waitSignalState = + isAbandonedMutex + ? WaitSignalState.NotWaiting_SignaledToSatisfyWaitWithAbandonedMutex + : WaitSignalState.NotWaiting_SignaledToSatisfyWait; + } + + _waitMonitor.Signal_Release(); + return !wouldAnyMutexReacquireCountOverflow; + } + + public void TrySignalToInterruptWaitOrRecordPendingInterrupt() + { + s_lock.VerifyIsLocked(); + + _waitMonitor.Acquire(); + + if (_waitSignalState != WaitSignalState.Waiting_Interruptible) + { + RecordPendingInterrupt(); + _waitMonitor.Release(); + return; + } + + if (_waitedCount != 0) + { + UnregisterWait(); + } + + Debug.Assert(_waitedObjectIndexThatSatisfiedWait < 0); + _waitSignalState = WaitSignalState.NotWaiting_SignaledToInterruptWait; + + _waitMonitor.Signal_Release(); + } + + private void RecordPendingInterrupt() + { + s_lock.VerifyIsLocked(); + _waitMonitor.VerifyIsLocked(); + + _isPendingInterrupt = 1; + } + + public bool CheckAndResetPendingInterrupt + { + get + { +#if DEBUG + Debug.Assert(s_lock.IsLocked || _waitMonitor.IsLocked); +#endif + + if (_isPendingInterrupt == 0) + { + return false; + } + _isPendingInterrupt = 0; + return true; + } + } + + private bool CheckAndResetPendingInterrupt_NotLocked + { + get + { + s_lock.VerifyIsNotLocked(); + _waitMonitor.VerifyIsNotLocked(); + + return Interlocked.CompareExchange(ref _isPendingInterrupt, 0, 1) != 0; + } + } + + public WaitableObject LockedMutexesHead + { + get + { + s_lock.VerifyIsLocked(); + return _lockedMutexesHead; + } + set + { + s_lock.VerifyIsLocked(); + _lockedMutexesHead = value; + } + } + + public void OnThreadExiting() + { + // Abandon locked mutexes. Acquired mutexes are prepended to the linked list, so the mutexes are abandoned in + // last-acquired-first-abandoned order. + s_lock.Acquire(); + try + { + while (true) + { + WaitableObject waitableObject = LockedMutexesHead; + if (waitableObject == null) + { + break; + } + + waitableObject.AbandonMutex(); + Debug.Assert(LockedMutexesHead != waitableObject); + } + } + finally + { + s_lock.Release(); + } + } + + public sealed class WaitedListNode + { + /// + /// For s registered with s, this provides information + /// about the thread that is waiting and the s it is waiting upon + /// + private readonly ThreadWaitInfo _waitInfo; + + /// + /// Index of the waited object corresponding to this node + /// + private readonly int _waitedObjectIndex; + + /// + /// Link in the linked list + /// + private WaitedListNode _previous, _next; + + public WaitedListNode(ThreadWaitInfo waitInfo, int waitedObjectIndex) + { + Debug.Assert(waitInfo != null); + Debug.Assert(waitedObjectIndex >= 0); + Debug.Assert(waitedObjectIndex < WaitHandle.MaxWaitHandles); + + _waitInfo = waitInfo; + _waitedObjectIndex = waitedObjectIndex; + } + + public ThreadWaitInfo WaitInfo + { + get + { + s_lock.VerifyIsLocked(); + return _waitInfo; + } + } + + public int WaitedObjectIndex + { + get + { + s_lock.VerifyIsLocked(); + return _waitedObjectIndex; + } + } + + public WaitedListNode Previous + { + get + { + s_lock.VerifyIsLocked(); + return _previous; + } + } + + public WaitedListNode Next + { + get + { + s_lock.VerifyIsLocked(); + return _next; + } + } + + public void RegisterWait(WaitableObject waitableObject) + { + s_lock.VerifyIsLocked(); + Debug.Assert(_waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(waitableObject != null); + + Debug.Assert(_previous == null); + Debug.Assert(_next == null); + + WaitedListNode tail = waitableObject.WaitersTail; + if (tail != null) + { + _previous = tail; + tail._next = this; + } + else + { + waitableObject.WaitersHead = this; + } + waitableObject.WaitersTail = this; + } + + public void RegisterPrioritizedWait(WaitableObject waitableObject) + { + s_lock.VerifyIsLocked(); + Debug.Assert(_waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(waitableObject != null); + + Debug.Assert(_previous == null); + Debug.Assert(_next == null); + + WaitedListNode head = waitableObject.WaitersHead; + if (head != null) + { + _next = head; + head._previous = this; + } + else + { + waitableObject.WaitersTail = this; + } + waitableObject.WaitersHead = this; + } + + public void UnregisterWait(WaitableObject waitableObject) + { + s_lock.VerifyIsLocked(); + Debug.Assert(waitableObject != null); + + WaitedListNode previous = _previous; + WaitedListNode next = _next; + + if (previous != null) + { + previous._next = next; + _previous = null; + } + else + { + Debug.Assert(waitableObject.WaitersHead == this); + waitableObject.WaitersHead = next; + } + + if (next != null) + { + next._previous = previous; + _next = null; + } + else + { + Debug.Assert(waitableObject.WaitersTail == this); + waitableObject.WaitersTail = previous; + } + } + } + + private enum WaitSignalState : byte + { + Waiting, + Waiting_Interruptible, + NotWaiting, + NotWaiting_SignaledToSatisfyWait, + NotWaiting_SignaledToSatisfyWaitWithAbandonedMutex, + NotWaiting_SignaledToAbortWaitDueToMaximumMutexReacquireCount, + NotWaiting_SignaledToInterruptWait + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs new file mode 100644 index 0000000000000..91d21a140c9de --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.Unix.cs @@ -0,0 +1,424 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.Win32.SafeHandles; + +namespace System.Threading +{ + /// # Wait subsystem + /// + /// ## Types + /// + /// + /// - Static API surface for dealing with synchronization objects that support multi-wait, and to put a thread into a wait + /// state, on Unix + /// - Any interaction with the wait subsystem from outside should go through APIs on this class, and should not directly + /// go through any of the nested classes + /// + /// + /// - An object that supports the features of , , and + /// . The handle of each of those classes is associated with a . + /// + /// + /// - Keeps information about a thread's wait and provides functionlity to put a thread into a wait state and to take it + /// out of a wait state. Each thread has an instance available through . + /// + /// + /// - Provides functionality to allocate a handle associated with a , to retrieve the object + /// from a handle, and to delete a handle. + /// + /// and + /// - These are "low level" in the sense they don't depend on this wait subsystem, and any waits done are not + /// interruptible + /// - is used for the process-wide lock + /// - is the main system dependency of the wait subsystem, and all waits are done through + /// it. It is backed by a C++ equivalent in CoreLib.Native's pal_threading.*, which wraps a pthread mutex/condition + /// pair. Each thread has an instance in , which is used to synchronize the + /// thread's wait state and for waiting. also uses an instance of + /// for waiting. + /// + /// ## Design goals + /// + /// Behave similarly to wait operations on Windows + /// - The design is similar to the one used by CoreCLR's PAL, but much simpler due to there being no need for supporting + /// process/thread waits, or cross-process multi-waits (which CoreCLR also does not support but there are many design + /// elements specific to it) + /// - Waiting + /// - A waiter keeps an array of objects on which it is waiting (see ). + /// - The waiter registers a with each + /// - The waiter waits on its own to go into a wait state + /// - Upon timeout, the waiter unregisters the wait and continues + /// - Sleeping + /// - Sleeping is just another way of waiting, only there would not be any waited objects + /// - Signaling + /// - A signaler iterates over waiters and tries to release waiters based on the signal count + /// - For each waiter, the signaler checks if the waiter's wait can be terminated + /// - When a waiter's wait can be terminated, the signaler does everything necesary before waking the waiter, such that + /// the waiter can simply continue after awakening, including unregistering the wait and assigning ownership if + /// applicable + /// - Interrupting + /// - Interrupting is just another way of signaling a waiting thread. The interrupter unregisters the wait and wakes the + /// waiter. + /// - Wait release fairness + /// - As mentioned above in how signaling works, waiters are released in fair order (first come, first served) + /// - This is mostly done to match the behavior of synchronization objects in Windows, which are also fair + /// - Events have an implicit requirement to be fair + /// - For a , Set/Reset in quick succession requires that it wakes up all waiters, + /// implying that the design cannot be to signal a thread to wake and have it check the state when it awakens some + /// time in the future + /// - For an , Set/Set in quick succession requires that it wakes up two threads, implying + /// that a Set/Wait in quick succession cannot have the calling thread accept its own signal if there is a waiter + /// - There is an advantage to being fair, as it guarantees that threads are only awakened when necessary. That is, a + /// thread will never wake up and find that it has to go back to sleep because the wait is not satisfied (except due + /// to spurious wakeups caused by external factors). + /// - Synchronization + /// - A process-wide lock is used to synchronize most operations and the signal state of all + /// s in the process. Given that it is recommended to use alternative synchronization + /// types (, , ) for single-wait + /// cases, it is probably not worth optimizing for the single-wait case. It is possible with a small design change to + /// bypass the lock and use interlocked operations for uncontended cases, but at the cost of making multi-waits more + /// complicated and slower. + /// - The wait state of a thread (), among other things, is synchornized + /// using the thread's , so signalers and interrupters acquire the monitor's + /// lock before checking the wait state of a thread and signaling the thread to wake up. + /// + /// Self-consistent in the event of any exception + /// - Try/finally is used extensively, including around any operation that could fail due to out-of-memory + /// + /// Decent balance between memory usage and performance + /// - is intended to be as small as possible while avoiding virtual calls and casts + /// - As is not commonly used and requires more state, some of its state is separated into + /// + /// - When support for cross-process objects is added, the current thought is to have an field that + /// is used for both cross-process state and ownership state. + /// + /// No allocation in typical cases of any operation except where necessary + /// - Since the maximum number of wait handles for a multi-wait operation is limited to + /// , arrays necessary for holding information about a multi-wait, and list nodes + /// necessary for registering a wait, are precreated with a low initial capacity that covers most typical cases + /// - Threads track owned mutexes by linking the instance into a linked list + /// . is itself a list node, + /// and is created along with the mutex . + /// + /// Minimal p/invokes in typical uncontended cases + /// - currently uses in the interest of + /// simplicity, which p/invokes and does a cast to get the from a handle + /// - Most of the wait subsystem is written in C#, so there is no initially required p/invoke + /// - , used by the process-wide lock , uses interlocked operations to + /// acquire and release the lock when there is no need to wait or to release a waiter. This is significantly faster than + /// using as a lock, which uses pthread mutex functionality through p/invoke. The lock is + /// typically not held for very long, especially since allocations inside the lock will be rare. + /// - Since provides mutual exclusion for the states of all s in the + /// process, any operation that does not involve waiting or releasing a wait can occur with minimal p/invokes + /// + [EagerStaticClassConstruction] // the wait subsystem is used during lazy class construction + internal static partial class WaitSubsystem + { + private static readonly LowLevelLock s_lock = new LowLevelLock(); + + private static SafeWaitHandle NewHandle(WaitableObject waitableObject) + { + IntPtr handle = HandleManager.NewHandle(waitableObject); + SafeWaitHandle safeWaitHandle = null; + try + { + safeWaitHandle = new SafeWaitHandle(handle, ownsHandle: true); + return safeWaitHandle; + } + finally + { + if (safeWaitHandle == null) + { + HandleManager.DeleteHandle(handle); + } + } + } + + public static SafeWaitHandle NewEvent(bool initiallySignaled, EventResetMode resetMode) + { + return NewHandle(WaitableObject.NewEvent(initiallySignaled, resetMode)); + } + + public static SafeWaitHandle NewSemaphore(int initialSignalCount, int maximumSignalCount) + { + return NewHandle(WaitableObject.NewSemaphore(initialSignalCount, maximumSignalCount)); + } + + public static SafeWaitHandle NewMutex(bool initiallyOwned) + { + WaitableObject waitableObject = WaitableObject.NewMutex(); + SafeWaitHandle safeWaitHandle = NewHandle(waitableObject); + if (!initiallyOwned) + { + return safeWaitHandle; + } + + // Acquire the mutex. A thread's has a reference to all es locked + // by the thread. See . So, acquire the lock only after all + // possibilities for exceptions have been exhausted. + ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; + bool acquiredLock = waitableObject.Wait(waitInfo, timeoutMilliseconds: 0, interruptible: false, prioritize: false) == 0; + Debug.Assert(acquiredLock); + return safeWaitHandle; + } + + public static void DeleteHandle(IntPtr handle) + { + HandleManager.DeleteHandle(handle); + } + + public static void SetEvent(IntPtr handle) + { + SetEvent(HandleManager.FromHandle(handle)); + } + + public static void SetEvent(WaitableObject waitableObject) + { + Debug.Assert(waitableObject != null); + + s_lock.Acquire(); + try + { + waitableObject.SignalEvent(); + } + finally + { + s_lock.Release(); + } + } + + public static void ResetEvent(IntPtr handle) + { + ResetEvent(HandleManager.FromHandle(handle)); + } + + public static void ResetEvent(WaitableObject waitableObject) + { + Debug.Assert(waitableObject != null); + + s_lock.Acquire(); + try + { + waitableObject.UnsignalEvent(); + } + finally + { + s_lock.Release(); + } + } + + public static int ReleaseSemaphore(IntPtr handle, int count) + { + Debug.Assert(count > 0); + return ReleaseSemaphore(HandleManager.FromHandle(handle), count); + } + + public static int ReleaseSemaphore(WaitableObject waitableObject, int count) + { + Debug.Assert(waitableObject != null); + Debug.Assert(count > 0); + + s_lock.Acquire(); + try + { + return waitableObject.SignalSemaphore(count); + } + finally + { + s_lock.Release(); + } + } + + public static void ReleaseMutex(IntPtr handle) + { + ReleaseMutex(HandleManager.FromHandle(handle)); + } + + public static void ReleaseMutex(WaitableObject waitableObject) + { + Debug.Assert(waitableObject != null); + + s_lock.Acquire(); + try + { + waitableObject.SignalMutex(); + } + finally + { + s_lock.Release(); + } + } + + public static int Wait(IntPtr handle, int timeoutMilliseconds, bool interruptible) + { + Debug.Assert(timeoutMilliseconds >= -1); + return Wait(HandleManager.FromHandle(handle), timeoutMilliseconds, interruptible); + } + + public static int Wait( + WaitableObject waitableObject, + int timeoutMilliseconds, + bool interruptible = true, + bool prioritize = false) + { + Debug.Assert(waitableObject != null); + Debug.Assert(timeoutMilliseconds >= -1); + + return waitableObject.Wait(Thread.CurrentThread.WaitInfo, timeoutMilliseconds, interruptible, prioritize); + } + + public static int Wait( + Span waitHandles, + bool waitForAll, + int timeoutMilliseconds) + { + Debug.Assert(waitHandles != null); + Debug.Assert(waitHandles.Length > 0); + Debug.Assert(waitHandles.Length <= WaitHandle.MaxWaitHandles); + Debug.Assert(timeoutMilliseconds >= -1); + + ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; + WaitableObject[] waitableObjects = waitInfo.GetWaitedObjectArray(waitHandles.Length); + bool success = false; + try + { + for (int i = 0; i < waitHandles.Length; ++i) + { + Debug.Assert(waitHandles[i] != IntPtr.Zero); + WaitableObject waitableObject = HandleManager.FromHandle(waitHandles[i]); + if (waitForAll) + { + // Check if this is a duplicate, as wait-for-all does not support duplicates. Including the parent + // loop, this becomes a brute force O(n^2) search, which is intended since the typical array length is + // short enough that this would actually be faster than other alternatives. Also, the worst case is not + // so bad considering that the array length is limited by . + for (int j = 0; j < i; ++j) + { + if (waitableObject == waitableObjects[j]) + { + throw new DuplicateWaitObjectException("waitHandles[" + i + ']'); + } + } + } + + waitableObjects[i] = waitableObject; + } + success = true; + } + finally + { + if (!success) + { + for (int i = 0; i < waitHandles.Length; ++i) + { + waitableObjects[i] = null; + } + } + } + + if (waitHandles.Length == 1) + { + WaitableObject waitableObject = waitableObjects[0]; + waitableObjects[0] = null; + return + waitableObject.Wait(waitInfo, timeoutMilliseconds, interruptible: true, prioritize : false); + } + + return + WaitableObject.Wait( + waitableObjects, + waitHandles.Length, + waitForAll, + waitInfo, + timeoutMilliseconds, + interruptible: true, + prioritize: false); + } + + public static int SignalAndWait( + IntPtr handleToSignal, + IntPtr handleToWaitOn, + int timeoutMilliseconds) + { + Debug.Assert(timeoutMilliseconds >= -1); + + return + SignalAndWait( + HandleManager.FromHandle(handleToSignal), + HandleManager.FromHandle(handleToWaitOn), + timeoutMilliseconds); + } + + public static int SignalAndWait( + WaitableObject waitableObjectToSignal, + WaitableObject waitableObjectToWaitOn, + int timeoutMilliseconds, + bool interruptible = true, + bool prioritize = false) + { + Debug.Assert(waitableObjectToSignal != null); + Debug.Assert(waitableObjectToWaitOn != null); + Debug.Assert(timeoutMilliseconds >= -1); + + ThreadWaitInfo waitInfo = Thread.CurrentThread.WaitInfo; + bool waitCalled = false; + s_lock.Acquire(); + try + { + // A pending interrupt does not signal the specified handle + if (interruptible && waitInfo.CheckAndResetPendingInterrupt) + { + throw new ThreadInterruptedException(); + } + + waitableObjectToSignal.Signal(1); + waitCalled = true; + return waitableObjectToWaitOn.Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize); + } + finally + { + // Once the wait function is called, it will release the lock + if (waitCalled) + { + s_lock.VerifyIsNotLocked(); + } + else + { + s_lock.Release(); + } + } + } + + public static void UninterruptibleSleep0() + { + ThreadWaitInfo.UninterruptibleSleep0(); + } + + public static void Sleep(int timeoutMilliseconds, bool interruptible = true) + { + ThreadWaitInfo.Sleep(timeoutMilliseconds, interruptible); + } + + public static void Interrupt(Thread thread) + { + Debug.Assert(thread != null); + + s_lock.Acquire(); + try + { + thread.WaitInfo.TrySignalToInterruptWaitOrRecordPendingInterrupt(); + } + finally + { + s_lock.Release(); + } + } + + public static void OnThreadExiting(Thread thread) + { + thread.WaitInfo.OnThreadExiting(); + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs new file mode 100644 index 0000000000000..8963e7b04415d --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Threading/WaitSubsystem.WaitableObject.Unix.cs @@ -0,0 +1,914 @@ +// 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.Threading +{ + internal static partial class WaitSubsystem + { + /// + /// A synchronization object that can participate in 's wait operations. + /// + /// Used by the wait subsystem on Unix, so this class cannot have any dependencies on the wait subsystem. + /// + public sealed class WaitableObject + { + private readonly WaitableObjectType _type; + private int _signalCount; + private readonly int _maximumSignalCount; + + /// + /// Only has a thread ownership requirement, and it's a less common type to be used, so + /// ownership info is separated to reduce the size. For other types, this is null. + /// + private readonly OwnershipInfo _ownershipInfo; + + /// + /// Linked list of information about waiting threads + /// + private ThreadWaitInfo.WaitedListNode _waitersHead, _waitersTail; + + private WaitableObject( + WaitableObjectType type, + int initialSignalCount, + int maximumSignalCount, + OwnershipInfo ownershipInfo) + { + Debug.Assert(initialSignalCount >= 0); + Debug.Assert(maximumSignalCount > 0); + Debug.Assert(initialSignalCount <= maximumSignalCount); + + _type = type; + _signalCount = initialSignalCount; + _maximumSignalCount = maximumSignalCount; + _ownershipInfo = ownershipInfo; + } + + public static WaitableObject NewEvent(bool initiallySignaled, EventResetMode resetMode) + { + Debug.Assert((resetMode == EventResetMode.AutoReset) || (resetMode == EventResetMode.ManualReset)); + + return + new WaitableObject( + resetMode == EventResetMode.ManualReset + ? WaitableObjectType.ManualResetEvent + : WaitableObjectType.AutoResetEvent, + initiallySignaled ? 1 : 0, + 1, + null); + } + + public static WaitableObject NewSemaphore(int initialSignalCount, int maximumSignalCount) + { + return new WaitableObject(WaitableObjectType.Semaphore, initialSignalCount, maximumSignalCount, null); + } + + public static WaitableObject NewMutex() + { + return new WaitableObject(WaitableObjectType.Mutex, 1, 1, new OwnershipInfo()); + } + + public void OnDeleteHandle() + { + s_lock.Acquire(); + try + { + if (IsMutex && !IsSignaled) + { + // A thread has a reference to all es locked by it, see + // . Abandon the mutex to remove the reference to it. + AbandonMutex(); + } + } + finally + { + s_lock.Release(); + } + } + + private bool IsEvent + { + get + { + s_lock.VerifyIsLocked(); + + bool result = _type <= WaitableObjectType.AutoResetEvent; + Debug.Assert(!result || _maximumSignalCount == 1); + Debug.Assert(!result || _ownershipInfo == null); + return result; + } + } + + private bool IsManualResetEvent + { + get + { + s_lock.VerifyIsLocked(); + + bool result = _type == WaitableObjectType.ManualResetEvent; + Debug.Assert(!result || _maximumSignalCount == 1); + Debug.Assert(!result || _ownershipInfo == null); + return result; + } + } + + private bool IsAutoResetEvent + { + get + { + s_lock.VerifyIsLocked(); + + bool result = _type == WaitableObjectType.AutoResetEvent; + Debug.Assert(!result || _maximumSignalCount == 1); + Debug.Assert(!result || _ownershipInfo == null); + return result; + } + } + + private bool IsSemaphore + { + get + { + s_lock.VerifyIsLocked(); + + bool result = _type == WaitableObjectType.Semaphore; + Debug.Assert(!result || _maximumSignalCount > 0); + Debug.Assert(!result || _ownershipInfo == null); + return result; + } + } + + private bool IsMutex + { + get + { + s_lock.VerifyIsLocked(); + + bool result = _type == WaitableObjectType.Mutex; + Debug.Assert(!result || _maximumSignalCount == 1); + Debug.Assert(!result || _ownershipInfo != null); + return result; + } + } + + private bool IsAbandonedMutex + { + get + { + s_lock.VerifyIsLocked(); + return IsMutex && _ownershipInfo.IsAbandoned; + } + } + + public ThreadWaitInfo.WaitedListNode WaitersHead + { + get + { + s_lock.VerifyIsLocked(); + return _waitersHead; + } + set + { + s_lock.VerifyIsLocked(); + _waitersHead = value; + } + } + + public ThreadWaitInfo.WaitedListNode WaitersTail + { + get + { + s_lock.VerifyIsLocked(); + return _waitersTail; + } + set + { + s_lock.VerifyIsLocked(); + _waitersTail = value; + } + } + + private bool IsSignaled + { + get + { + s_lock.VerifyIsLocked(); + + bool isSignaled = _signalCount != 0; + Debug.Assert(isSignaled || !IsMutex || _ownershipInfo.Thread != null); + return isSignaled; + } + } + + private void AcceptSignal(ThreadWaitInfo waitInfo) + { + s_lock.VerifyIsLocked(); + Debug.Assert(IsSignaled); + + switch (_type) + { + case WaitableObjectType.ManualResetEvent: + return; + + case WaitableObjectType.AutoResetEvent: + case WaitableObjectType.Semaphore: + --_signalCount; + return; + + default: + Debug.Assert(_type == WaitableObjectType.Mutex); + --_signalCount; + + Debug.Assert(_ownershipInfo.Thread == null); + _ownershipInfo.AssignOwnership(this, waitInfo); + return; + } + } + + public int Wait(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) + { + Debug.Assert(waitInfo != null); + Debug.Assert(waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(timeoutMilliseconds >= -1); + + s_lock.Acquire(); + + if (interruptible && waitInfo.CheckAndResetPendingInterrupt) + { + s_lock.Release(); + throw new ThreadInterruptedException(); + } + + return Wait_Locked(waitInfo, timeoutMilliseconds, interruptible, prioritize); + } + + /// + /// This function does not check for a pending thread interrupt. Callers are expected to do that soon after + /// acquiring . + /// + public int Wait_Locked(ThreadWaitInfo waitInfo, int timeoutMilliseconds, bool interruptible, bool prioritize) + { + s_lock.VerifyIsLocked(); + Debug.Assert(waitInfo != null); + Debug.Assert(waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(timeoutMilliseconds >= -1); + Debug.Assert(!interruptible || !waitInfo.CheckAndResetPendingInterrupt); + + bool needToWait = false; + try + { + if (IsSignaled) + { + bool isAbandoned = IsAbandonedMutex; + AcceptSignal(waitInfo); + return isAbandoned ? WaitHandle.WaitAbandoned : WaitHandle.WaitSuccess; + } + + if (IsMutex && _ownershipInfo.Thread == waitInfo.Thread) + { + if (!_ownershipInfo.CanIncrementReacquireCount) + { + throw new OverflowException(SR.Overflow_MutexReacquireCount); + } + _ownershipInfo.IncrementReacquireCount(); + return WaitHandle.WaitSuccess; + } + + if (timeoutMilliseconds == 0) + { + return WaitHandle.WaitTimeout; + } + + WaitableObject[] waitableObjects = waitInfo.GetWaitedObjectArray(1); + waitableObjects[0] = this; + waitInfo.RegisterWait(1, prioritize, isWaitForAll: false); + needToWait = true; + } + finally + { + // Once the wait function is called, it will release the lock + if (!needToWait) + { + s_lock.Release(); + } + } + + return + waitInfo.Wait( + timeoutMilliseconds, + interruptible, + isSleep: false); + } + + public static int Wait( + WaitableObject[] waitableObjects, + int count, + bool waitForAll, + ThreadWaitInfo waitInfo, + int timeoutMilliseconds, + bool interruptible, + bool prioritize) + { + s_lock.VerifyIsNotLocked(); + Debug.Assert(waitInfo != null); + Debug.Assert(waitInfo.Thread == Thread.CurrentThread); + + Debug.Assert(waitableObjects != null); + Debug.Assert(waitableObjects.Length >= count); + Debug.Assert(count > 1); + Debug.Assert(timeoutMilliseconds >= -1); + + bool needToWait = false; + s_lock.Acquire(); + try + { + if (interruptible && waitInfo.CheckAndResetPendingInterrupt) + { + throw new ThreadInterruptedException(); + } + + if (!waitForAll) + { + // Check if any is already signaled + for (int i = 0; i < count; ++i) + { + WaitableObject waitableObject = waitableObjects[i]; + Debug.Assert(waitableObject != null); + + if (waitableObject.IsSignaled) + { + bool isAbandoned = waitableObject.IsAbandonedMutex; + waitableObject.AcceptSignal(waitInfo); + if (isAbandoned) + { + return WaitHandle.WaitAbandoned + i; + } + return WaitHandle.WaitSuccess + i; + } + + if (waitableObject.IsMutex) + { + OwnershipInfo ownershipInfo = waitableObject._ownershipInfo; + if (ownershipInfo.Thread == waitInfo.Thread) + { + if (!ownershipInfo.CanIncrementReacquireCount) + { + throw new OverflowException(SR.Overflow_MutexReacquireCount); + } + ownershipInfo.IncrementReacquireCount(); + return WaitHandle.WaitSuccess + i; + } + } + } + } + else + { + // Check if all are already signaled + bool areAllSignaled = true; + bool isAnyAbandonedMutex = false; + for (int i = 0; i < count; ++i) + { + WaitableObject waitableObject = waitableObjects[i]; + Debug.Assert(waitableObject != null); + + if (waitableObject.IsSignaled) + { + if (!isAnyAbandonedMutex && waitableObject.IsAbandonedMutex) + { + isAnyAbandonedMutex = true; + } + continue; + } + + if (waitableObject.IsMutex) + { + OwnershipInfo ownershipInfo = waitableObject._ownershipInfo; + if (ownershipInfo.Thread == waitInfo.Thread) + { + if (!ownershipInfo.CanIncrementReacquireCount) + { + throw new OverflowException(SR.Overflow_MutexReacquireCount); + } + continue; + } + } + + areAllSignaled = false; + break; + } + + if (areAllSignaled) + { + for (int i = 0; i < count; ++i) + { + WaitableObject waitableObject = waitableObjects[i]; + if (waitableObject.IsSignaled) + { + waitableObject.AcceptSignal(waitInfo); + continue; + } + + Debug.Assert(waitableObject.IsMutex); + OwnershipInfo ownershipInfo = waitableObject._ownershipInfo; + Debug.Assert(ownershipInfo.Thread == waitInfo.Thread); + ownershipInfo.IncrementReacquireCount(); + } + + if (isAnyAbandonedMutex) + { + throw new AbandonedMutexException(); + } + return WaitHandle.WaitSuccess; + } + } + + if (timeoutMilliseconds == 0) + { + return WaitHandle.WaitTimeout; + } + + waitableObjects = null; // no need to clear this anymore, RegisterWait / Wait will take over from here + waitInfo.RegisterWait(count, prioritize, waitForAll); + needToWait = true; + } + finally + { + if (waitableObjects != null) + { + for (int i = 0; i < count; ++i) + { + waitableObjects[i] = null; + } + } + + // Once the wait function is called, it will release the lock + if (!needToWait) + { + s_lock.Release(); + } + } + + return waitInfo.Wait(timeoutMilliseconds, interruptible, isSleep: false); + } + + public static bool WouldWaitForAllBeSatisfiedOrAborted( + Thread waitingThread, + WaitableObject[] waitedObjects, + int waitedCount, + int signaledWaitedObjectIndex, + ref bool wouldAnyMutexReacquireCountOverflow, + ref bool isAnyAbandonedMutex) + { + s_lock.VerifyIsLocked(); + Debug.Assert(waitingThread != null); + Debug.Assert(waitingThread != Thread.CurrentThread); + Debug.Assert(waitedObjects != null); + Debug.Assert(waitedObjects.Length >= waitedCount); + Debug.Assert(waitedCount > 1); + Debug.Assert(signaledWaitedObjectIndex >= 0); + Debug.Assert(signaledWaitedObjectIndex <= WaitHandle.MaxWaitHandles); + Debug.Assert(!wouldAnyMutexReacquireCountOverflow); + + for (int i = 0; i < waitedCount; ++i) + { + Debug.Assert(waitedObjects[i] != null); + if (i == signaledWaitedObjectIndex) + { + continue; + } + + WaitableObject waitedObject = waitedObjects[i]; + if (waitedObject.IsSignaled) + { + if (!isAnyAbandonedMutex && waitedObject.IsAbandonedMutex) + { + isAnyAbandonedMutex = true; + } + continue; + } + + if (waitedObject.IsMutex) + { + OwnershipInfo ownershipInfo = waitedObject._ownershipInfo; + if (ownershipInfo.Thread == waitingThread) + { + if (!ownershipInfo.CanIncrementReacquireCount) + { + // This will cause the wait to be aborted without accepting any signals + wouldAnyMutexReacquireCountOverflow = true; + return true; + } + continue; + } + } + + return false; + } + + return true; + } + + public static void SatisfyWaitForAll( + ThreadWaitInfo waitInfo, + WaitableObject[] waitedObjects, + int waitedCount, + int signaledWaitedObjectIndex) + { + s_lock.VerifyIsLocked(); + Debug.Assert(waitInfo != null); + Debug.Assert(waitInfo.Thread != Thread.CurrentThread); + Debug.Assert(waitedObjects != null); + Debug.Assert(waitedObjects.Length >= waitedCount); + Debug.Assert(waitedCount > 1); + Debug.Assert(signaledWaitedObjectIndex >= 0); + Debug.Assert(signaledWaitedObjectIndex <= WaitHandle.MaxWaitHandles); + + for (int i = 0; i < waitedCount; ++i) + { + Debug.Assert(waitedObjects[i] != null); + if (i == signaledWaitedObjectIndex) + { + continue; + } + + WaitableObject waitedObject = waitedObjects[i]; + if (waitedObject.IsSignaled) + { + waitedObject.AcceptSignal(waitInfo); + continue; + } + + Debug.Assert(waitedObject.IsMutex); + OwnershipInfo ownershipInfo = waitedObject._ownershipInfo; + Debug.Assert(ownershipInfo.Thread == waitInfo.Thread); + ownershipInfo.IncrementReacquireCount(); + } + } + + public void Signal(int count) + { + s_lock.VerifyIsLocked(); + Debug.Assert(count > 0); + + switch (_type) + { + case WaitableObjectType.ManualResetEvent: + Debug.Assert(count == 1); + SignalManualResetEvent(); + break; + + case WaitableObjectType.AutoResetEvent: + Debug.Assert(count == 1); + SignalAutoResetEvent(); + break; + + case WaitableObjectType.Semaphore: + SignalSemaphore(count); + break; + + default: + Debug.Assert(count == 1); + SignalMutex(); + break; + } + } + + public void SignalEvent() + { + s_lock.VerifyIsLocked(); + + switch (_type) + { + case WaitableObjectType.ManualResetEvent: + SignalManualResetEvent(); + break; + + case WaitableObjectType.AutoResetEvent: + SignalAutoResetEvent(); + break; + + default: + WaitHandle.ThrowInvalidHandleException(); + break; + } + } + + private void SignalManualResetEvent() + { + s_lock.VerifyIsLocked(); + Debug.Assert(IsManualResetEvent); + + if (IsSignaled) + { + return; + } + + for (ThreadWaitInfo.WaitedListNode waiterNode = _waitersHead, nextWaiterNode; + waiterNode != null; + waiterNode = nextWaiterNode) + { + // Signaling a waiter will unregister the waiter node, so keep the next node before trying + nextWaiterNode = waiterNode.Next; + + waiterNode.WaitInfo.TrySignalToSatisfyWait(waiterNode, isAbandonedMutex: false); + } + + _signalCount = 1; + } + + private void SignalAutoResetEvent() + { + s_lock.VerifyIsLocked(); + Debug.Assert(IsAutoResetEvent); + + if (IsSignaled) + { + return; + } + + for (ThreadWaitInfo.WaitedListNode waiterNode = _waitersHead, nextWaiterNode; + waiterNode != null; + waiterNode = nextWaiterNode) + { + // Signaling a waiter will unregister the waiter node, but it may only abort the wait without satisfying the + // wait, in which case we would try to signal another waiter. So, keep the next node before trying. + nextWaiterNode = waiterNode.Next; + + if (waiterNode.WaitInfo.TrySignalToSatisfyWait(waiterNode, isAbandonedMutex: false)) + { + return; + } + } + + _signalCount = 1; + } + + public void UnsignalEvent() + { + s_lock.VerifyIsLocked(); + + if (!IsEvent) + { + WaitHandle.ThrowInvalidHandleException(); + } + + if (IsSignaled) + { + --_signalCount; + } + } + + public int SignalSemaphore(int count) + { + s_lock.VerifyIsLocked(); + Debug.Assert(count > 0); + + if (!IsSemaphore) + { + WaitHandle.ThrowInvalidHandleException(); + } + + int oldSignalCount = _signalCount; + Debug.Assert(oldSignalCount <= _maximumSignalCount); + if (count > _maximumSignalCount - oldSignalCount) + { + throw new SemaphoreFullException(); + } + + if (oldSignalCount != 0) + { + _signalCount = oldSignalCount + count; + return oldSignalCount; + } + + for (ThreadWaitInfo.WaitedListNode waiterNode = _waitersHead, nextWaiterNode; + waiterNode != null; + waiterNode = nextWaiterNode) + { + // Signaling the waiter will unregister the waiter node, so keep the next node before trying + nextWaiterNode = waiterNode.Next; + + if (waiterNode.WaitInfo.TrySignalToSatisfyWait(waiterNode, isAbandonedMutex: false) && --count == 0) + { + return oldSignalCount; + } + } + + _signalCount = count; + return oldSignalCount; + } + + public void SignalMutex() + { + s_lock.VerifyIsLocked(); + + if (!IsMutex) + { + WaitHandle.ThrowInvalidHandleException(); + } + + if (IsSignaled || _ownershipInfo.Thread != Thread.CurrentThread) + { + throw new ApplicationException(SR.Arg_SynchronizationLockException); + } + + if (!_ownershipInfo.TryDecrementReacquireCount()) + { + SignalMutex(isAbandoned: false); + } + } + + public void AbandonMutex() + { + s_lock.VerifyIsLocked(); + + Debug.Assert(IsMutex); + Debug.Assert(!IsSignaled); + + // Typically, a mutex is abandoned before a thread that owns the mutex exits. However, any thread may + // abandon a mutex since any thread may delete a mutex handle. See . Although + // Windows does not release waiters when a mutex is abandoned by deleting its handle, this implementation + // treats that case as abandonment as well and releases waiters, as it's a bit more robust. As the handle + // would be deleted shortly afterwards, it would otherwise not be possible to signal the mutex to release + // waiters. + SignalMutex(isAbandoned: true); + } + + private void SignalMutex(bool isAbandoned) + { + s_lock.VerifyIsLocked(); + + Debug.Assert(IsMutex); + Debug.Assert(!IsSignaled); + + _ownershipInfo.RelinquishOwnership(this, isAbandoned); + + for (ThreadWaitInfo.WaitedListNode waiterNode = _waitersHead, nextWaiterNode; + waiterNode != null; + waiterNode = nextWaiterNode) + { + // Signaling a waiter will unregister the waiter node, but it may only abort the wait without satisfying the + // wait, in which case we would try to signal another waiter. So, keep the next node before trying. + nextWaiterNode = waiterNode.Next; + + ThreadWaitInfo waitInfo = waiterNode.WaitInfo; + if (waitInfo.TrySignalToSatisfyWait(waiterNode, isAbandoned)) + { + _ownershipInfo.AssignOwnership(this, waitInfo); + return; + } + } + + _signalCount = 1; + } + + private sealed class OwnershipInfo + { + private Thread _thread; + private int _reacquireCount; + private bool _isAbandoned; + + /// + /// Link in the linked list + /// + private WaitableObject _previous; + + /// + /// Link in the linked list + /// + private WaitableObject _next; + + public Thread Thread + { + get + { + s_lock.VerifyIsLocked(); + return _thread; + } + } + + public bool IsAbandoned + { + get + { + s_lock.VerifyIsLocked(); + return _isAbandoned; + } + } + + public void AssignOwnership(WaitableObject waitableObject, ThreadWaitInfo waitInfo) + { + s_lock.VerifyIsLocked(); + + Debug.Assert(waitableObject != null); + Debug.Assert(waitableObject.IsMutex); + Debug.Assert(waitableObject._ownershipInfo == this); + + Debug.Assert(_thread == null); + Debug.Assert(_reacquireCount == 0); + Debug.Assert(_previous == null); + Debug.Assert(_next == null); + + _thread = waitInfo.Thread; + _isAbandoned = false; + + WaitableObject head = waitInfo.LockedMutexesHead; + if (head != null) + { + _next = head; + head._ownershipInfo._previous = waitableObject; + } + waitInfo.LockedMutexesHead = waitableObject; + } + + public void RelinquishOwnership(WaitableObject waitableObject, bool isAbandoned) + { + s_lock.VerifyIsLocked(); + + Debug.Assert(waitableObject != null); + Debug.Assert(waitableObject.IsMutex); + Debug.Assert(waitableObject._ownershipInfo == this); + + Debug.Assert(_thread != null); + ThreadWaitInfo waitInfo = _thread.WaitInfo; + Debug.Assert(isAbandoned ? _reacquireCount >= 0 : _reacquireCount == 0); + Debug.Assert(!_isAbandoned); + + _thread = null; + if (isAbandoned) + { + _reacquireCount = 0; + _isAbandoned = isAbandoned; + } + + WaitableObject previous = _previous; + WaitableObject next = _next; + + if (previous != null) + { + previous._ownershipInfo._next = next; + _previous = null; + } + else + { + Debug.Assert(waitInfo.LockedMutexesHead == waitableObject); + waitInfo.LockedMutexesHead = next; + } + + if (next != null) + { + next._ownershipInfo._previous = previous; + _next = null; + } + } + + public bool CanIncrementReacquireCount + { + get + { + s_lock.VerifyIsLocked(); + + Debug.Assert(_thread != null); + Debug.Assert(_reacquireCount >= 0); + Debug.Assert(!_isAbandoned); + + return _reacquireCount + 1 > _reacquireCount; + } + } + + public void IncrementReacquireCount() + { + Debug.Assert(CanIncrementReacquireCount); + ++_reacquireCount; + } + + public bool TryDecrementReacquireCount() + { + s_lock.VerifyIsLocked(); + + Debug.Assert(_thread == Thread.CurrentThread); + Debug.Assert(_reacquireCount >= 0); + Debug.Assert(!_isAbandoned); + + if (_reacquireCount == 0) + { + return false; + } + --_reacquireCount; + return true; + } + } + + private enum WaitableObjectType : byte + { + ManualResetEvent, + AutoResetEvent, + Semaphore, + Mutex + } + } + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/ValueTuple.cs b/src/libraries/System.Private.CoreLib/src/System/ValueTuple.cs index ad92422b6c804..73134ea840a85 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ValueTuple.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ValueTuple.cs @@ -58,9 +58,9 @@ bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) int IComparable.CompareTo(object? other) { - if (other == null) return 1; + if (other is null) return 1; - if (!(other is ValueTuple)) + if (other is not ValueTuple) { ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } @@ -83,9 +83,9 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; + if (other is null) return 1; - if (!(other is ValueTuple)) + if (other is not ValueTuple) { ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } @@ -291,7 +291,7 @@ public ValueTuple(T1 item1) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -309,27 +309,23 @@ public bool Equals(ValueTuple other) return EqualityComparer.Default.Equals(Item1, other.Item1); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return Comparer.Default.Compare(Item1, objTuple.Item1); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - var objTuple = (ValueTuple)other; - - return Comparer.Default.Compare(Item1, objTuple.Item1); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -347,16 +343,17 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return comparer.Compare(Item1, objTuple.Item1); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - var objTuple = (ValueTuple)other; - - return comparer.Compare(Item1, objTuple.Item1); + return 1; } /// @@ -466,7 +463,7 @@ public ValueTuple(T1 item1, T2 item2) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -502,26 +499,24 @@ public bool Equals(ValueTuple other) /// implementation. If this method call returns , the method is /// called again and passed the values of the two instances. /// - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -542,19 +537,20 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + return comparer.Compare(Item2, objTuple.Item2); + } - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item2, objTuple.Item2); + return 1; } /// @@ -673,7 +669,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -693,27 +689,25 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Item3, other.Item3); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -737,22 +731,23 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + return comparer.Compare(Item3, objTuple.Item3); + } - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item3, objTuple.Item3); + return 1; } /// @@ -879,7 +874,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -900,28 +895,26 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Item4, other.Item4); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3) - && comparer.Equals(Item4, objTuple.Item4); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3) && + comparer.Equals(Item4, vt.Item4); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -948,25 +941,26 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + return comparer.Compare(Item4, objTuple.Item4); + } - c = comparer.Compare(Item3, objTuple.Item3); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item4, objTuple.Item4); + return 1; } /// @@ -1103,7 +1097,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -1125,29 +1119,26 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Item5, other.Item5); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3) - && comparer.Equals(Item4, objTuple.Item4) - && comparer.Equals(Item5, objTuple.Item5); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3) && + comparer.Equals(Item5, vt.Item5); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -1177,28 +1168,29 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; - c = comparer.Compare(Item3, objTuple.Item3); - if (c != 0) return c; + return comparer.Compare(Item5, objTuple.Item5); + } - c = comparer.Compare(Item4, objTuple.Item4); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item5, objTuple.Item5); + return 1; } /// @@ -1345,7 +1337,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -1368,30 +1360,27 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Item6, other.Item6); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3) - && comparer.Equals(Item4, objTuple.Item4) - && comparer.Equals(Item5, objTuple.Item5) - && comparer.Equals(Item6, objTuple.Item6); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3) && + comparer.Equals(Item5, vt.Item5) && + comparer.Equals(Item6, vt.Item6); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -1424,31 +1413,32 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; - c = comparer.Compare(Item3, objTuple.Item3); - if (c != 0) return c; + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; - c = comparer.Compare(Item4, objTuple.Item4); - if (c != 0) return c; + return comparer.Compare(Item6, objTuple.Item6); + } - c = comparer.Compare(Item5, objTuple.Item5); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item6, objTuple.Item6); + return 1; } /// @@ -1605,7 +1595,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -1629,31 +1619,28 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Item7, other.Item7); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3) - && comparer.Equals(Item4, objTuple.Item4) - && comparer.Equals(Item5, objTuple.Item5) - && comparer.Equals(Item6, objTuple.Item6) - && comparer.Equals(Item7, objTuple.Item7); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3) && + comparer.Equals(Item5, vt.Item5) && + comparer.Equals(Item6, vt.Item6) && + comparer.Equals(Item7, vt.Item7); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -1689,34 +1676,35 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; - c = comparer.Compare(Item3, objTuple.Item3); - if (c != 0) return c; + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; - c = comparer.Compare(Item4, objTuple.Item4); - if (c != 0) return c; + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; - c = comparer.Compare(Item5, objTuple.Item5); - if (c != 0) return c; + return comparer.Compare(Item7, objTuple.Item7); + } - c = comparer.Compare(Item6, objTuple.Item6); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Item7, objTuple.Item7); + return 1; } /// @@ -1859,7 +1847,7 @@ public struct ValueTuple /// The value of the tuple's eight component. public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) { - if (!(rest is IValueTupleInternal)) + if (rest is not IValueTupleInternal) { throw new ArgumentException(SR.ArgumentException_ValueTupleLastArgumentNotAValueTuple); } @@ -1889,7 +1877,7 @@ public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 /// public override bool Equals(object? obj) { - return obj is ValueTuple && Equals((ValueTuple)obj); + return obj is ValueTuple tuple && Equals(tuple); } /// @@ -1914,32 +1902,29 @@ public bool Equals(ValueTuple other) && EqualityComparer.Default.Equals(Rest, other.Rest); } - bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) - { - if (other == null || !(other is ValueTuple)) return false; - - var objTuple = (ValueTuple)other; - - return comparer.Equals(Item1, objTuple.Item1) - && comparer.Equals(Item2, objTuple.Item2) - && comparer.Equals(Item3, objTuple.Item3) - && comparer.Equals(Item4, objTuple.Item4) - && comparer.Equals(Item5, objTuple.Item5) - && comparer.Equals(Item6, objTuple.Item6) - && comparer.Equals(Item7, objTuple.Item7) - && comparer.Equals(Rest, objTuple.Rest); - } + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) => + other is ValueTuple vt && + comparer.Equals(Item1, vt.Item1) && + comparer.Equals(Item2, vt.Item2) && + comparer.Equals(Item3, vt.Item3) && + comparer.Equals(Item5, vt.Item5) && + comparer.Equals(Item6, vt.Item6) && + comparer.Equals(Item7, vt.Item7) && + comparer.Equals(Rest, vt.Rest); int IComparable.CompareTo(object? other) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { + if (other is ValueTuple objTuple) + { + return CompareTo(objTuple); + } + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); } - return CompareTo((ValueTuple)other); + return 1; } /// Compares this instance to a specified instance and returns an indication of their relative values. @@ -1978,37 +1963,38 @@ public int CompareTo(ValueTuple other) int IStructuralComparable.CompareTo(object? other, IComparer comparer) { - if (other == null) return 1; - - if (!(other is ValueTuple)) + if (other is not null) { - ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); - } + if (other is ValueTuple objTuple) + { + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; - var objTuple = (ValueTuple)other; + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; - int c = comparer.Compare(Item1, objTuple.Item1); - if (c != 0) return c; + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; - c = comparer.Compare(Item2, objTuple.Item2); - if (c != 0) return c; + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; - c = comparer.Compare(Item3, objTuple.Item3); - if (c != 0) return c; + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; - c = comparer.Compare(Item4, objTuple.Item4); - if (c != 0) return c; + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; - c = comparer.Compare(Item5, objTuple.Item5); - if (c != 0) return c; + c = comparer.Compare(Item7, objTuple.Item7); + if (c != 0) return c; - c = comparer.Compare(Item6, objTuple.Item6); - if (c != 0) return c; + return comparer.Compare(Rest, objTuple.Rest); + } - c = comparer.Compare(Item7, objTuple.Item7); - if (c != 0) return c; + ThrowHelper.ThrowArgumentException_TupleIncorrectType(this); + } - return comparer.Compare(Rest, objTuple.Rest); + return 1; } /// @@ -2018,7 +2004,7 @@ int IStructuralComparable.CompareTo(object? other, IComparer comparer) public override int GetHashCode() { // We want to have a limited hash in this case. We'll use the first 7 elements of the tuple - if (!(Rest is IValueTupleInternal)) + if (Rest is not IValueTupleInternal) { return HashCode.Combine(Item1?.GetHashCode() ?? 0, Item2?.GetHashCode() ?? 0, @@ -2097,7 +2083,7 @@ int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) private int GetHashCodeCore(IEqualityComparer comparer) { // We want to have a limited hash in this case. We'll use the first 7 elements of the tuple - if (!(Rest is IValueTupleInternal rest)) + if (Rest is not IValueTupleInternal rest) { return HashCode.Combine(comparer.GetHashCode(Item1!), comparer.GetHashCode(Item2!), comparer.GetHashCode(Item3!), comparer.GetHashCode(Item4!), comparer.GetHashCode(Item5!), comparer.GetHashCode(Item6!), diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs index 5957cb4e8d93f..968d386c90921 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/HelperMarshal.cs @@ -60,6 +60,12 @@ private static void InvokeString(string s) _stringResource = s; } + internal static string _stringResource2; + private static void InvokeString2(string s) + { + _stringResource2 = s; + } + internal static string _marshalledString; private static string InvokeMarshalString() { diff --git a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs index 1c1447201732b..e15716f4f348e 100644 --- a/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs +++ b/src/libraries/System.Private.Runtime.InteropServices.JavaScript/tests/System/Runtime/InteropServices/JavaScript/MarshalTests.cs @@ -748,5 +748,104 @@ public static void CannotUnboxUint64 () ); Assert.StartsWith("Error: int64 not available", exc.Message); } + + [Fact] + public static void BareStringArgumentsAreNotInterned() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var jsLiteral = ""hello world""; + App.call_test_method (""InvokeString"", [ jsLiteral ]); + App.call_test_method (""InvokeString2"", [ jsLiteral ]); + "); + Assert.Equal("hello world", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.False(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } + + [Fact] + public static void InternedStringSignaturesAreInternedOnJavascriptSide() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var sym = ""interned string""; + App.call_test_method (""InvokeString"", [ sym ], ""S""); + App.call_test_method (""InvokeString2"", [ sym ], ""S""); + "); + Assert.Equal("interned string", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } + + [Fact] + public static void OnceAJSStringIsInternedItIsAlwaysUsedIfPossible() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var sym = ""interned string 2""; + App.call_test_method (""InvokeString"", [ sym ], ""S""); + App.call_test_method (""InvokeString2"", [ sym ], ""s""); + "); + Assert.Equal("interned string 2", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } + + [Fact] + public static void ManuallyInternString() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var sym = BINDING.mono_intern_string(""interned string 3""); + App.call_test_method (""InvokeString"", [ sym ], ""s""); + App.call_test_method (""InvokeString2"", [ sym ], ""s""); + "); + Assert.Equal("interned string 3", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } + + [Fact] + public static void LargeStringsAreNotAutomaticallyLocatedInInternTable() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var s = ""long interned string""; + for (var i = 0; i < 1024; i++) + s += String(i % 10); + var sym = BINDING.mono_intern_string(s); + App.call_test_method (""InvokeString"", [ sym ], ""S""); + App.call_test_method (""InvokeString2"", [ sym ], ""s""); + "); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.False(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } + + [Fact] + public static void CanInternVeryManyStrings() + { + HelperMarshal._stringResource = null; + Runtime.InvokeJS(@" + for (var i = 0; i < 10240; i++) + BINDING.mono_intern_string('s' + i); + App.call_test_method (""InvokeString"", [ 's5000' ], ""S""); + "); + Assert.Equal("s5000", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, string.IsInterned(HelperMarshal._stringResource)); + } + + [Fact] + public static void SymbolsAreMarshaledAsStrings() + { + HelperMarshal._stringResource = HelperMarshal._stringResource2 = null; + Runtime.InvokeJS(@" + var jsLiteral = Symbol(""custom symbol""); + App.call_test_method (""InvokeString"", [ jsLiteral ]); + App.call_test_method (""InvokeString2"", [ jsLiteral ]); + "); + Assert.Equal("custom symbol", HelperMarshal._stringResource); + Assert.Equal(HelperMarshal._stringResource, HelperMarshal._stringResource2); + Assert.True(Object.ReferenceEquals(HelperMarshal._stringResource, HelperMarshal._stringResource2)); + } } } diff --git a/src/libraries/System.Private.Uri/src/System/IriHelper.cs b/src/libraries/System.Private.Uri/src/System/IriHelper.cs index f357a44b9bd2b..ebc433bb1e2f5 100644 --- a/src/libraries/System.Private.Uri/src/System/IriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/IriHelper.cs @@ -89,8 +89,8 @@ internal static bool CheckIsReserved(char ch, UriComponents component) internal static unsafe string EscapeUnescapeIri(char* pInput, int start, int end, UriComponents component) { int size = end - start; - ValueStringBuilder dest = size <= 256 - ? new ValueStringBuilder(stackalloc char[256]) + var dest = size <= Uri.StackallocThreshold + ? new ValueStringBuilder(stackalloc char[Uri.StackallocThreshold]) : new ValueStringBuilder(size); Span maxUtf8EncodedSpan = stackalloc byte[4]; diff --git a/src/libraries/System.Private.Uri/src/System/Uri.cs b/src/libraries/System.Private.Uri/src/System/Uri.cs index 7653dd4ab23a1..cfa15a2d18e9f 100644 --- a/src/libraries/System.Private.Uri/src/System/Uri.cs +++ b/src/libraries/System.Private.Uri/src/System/Uri.cs @@ -34,6 +34,7 @@ public partial class Uri : ISerializable public static readonly string UriSchemeNetPipe = UriParser.NetPipeUri.SchemeName; public static readonly string SchemeDelimiter = "://"; + internal const int StackallocThreshold = 512; internal const int c_MaxUriBufferSize = 0xFFF0; private const int c_MaxUriSchemeName = 1024; @@ -1019,7 +1020,7 @@ private string GetLocalPath() { // suspecting not compressed path // For a dos path we won't compress the "x:" part if found /../ sequences - result = Compress(result, (ushort)(IsDosPath ? pathStart + 2 : pathStart), ref count, _syntax); + Compress(result, IsDosPath ? pathStart + 2 : pathStart, ref count, _syntax); } // We don't know whether all slashes were the back ones @@ -1184,7 +1185,7 @@ public string IdnHost else if (hostType == Flags.BasicHostType && InFact(Flags.HostNotCanonical | Flags.E_HostNotCanonical)) { // Unescape everything - ValueStringBuilder dest = new ValueStringBuilder(stackalloc char[256]); + var dest = new ValueStringBuilder(stackalloc char[StackallocThreshold]); UriHelper.UnescapeString(host, 0, host.Length, ref dest, c_DummyChar, c_DummyChar, c_DummyChar, @@ -2631,28 +2632,24 @@ private string GetUnescapedParts(UriComponents uriParts, UriFormat formatAs) private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat formatAs) { EnsureHostString(false); - string stemp = (parts & UriComponents.Host) == 0 ? string.Empty : _info.Host!; - // we reserve more space than required because a canonical Ipv6 Host - // may take more characters than in original _string - // Also +3 is for :// and +1 is for absent first slash - // Also we may escape every character, hence multiplying by 12 - // UTF-8 can use up to 4 bytes per char * 3 chars per byte (%A4) = 12 encoded chars - int count = (_info.Offset.End - _info.Offset.User) * (formatAs == UriFormat.UriEscaped ? 12 : 1); - char[] chars = new char[stemp.Length + count + _syntax.SchemeName.Length + 3 + 1]; - count = 0; + + string str = _string; + + var dest = str.Length <= StackallocThreshold + ? new ValueStringBuilder(stackalloc char[StackallocThreshold]) + : new ValueStringBuilder(str.Length); //Scheme and slashes if ((parts & UriComponents.Scheme) != 0) { - _syntax.SchemeName.CopyTo(0, chars, count, _syntax.SchemeName.Length); - count += _syntax.SchemeName.Length; + dest.Append(_syntax.SchemeName); if (parts != UriComponents.Scheme) { - chars[count++] = ':'; + dest.Append(':'); if (InFact(Flags.AuthorityFound)) { - chars[count++] = '/'; - chars[count++] = '/'; + dest.Append('/'); + dest.Append('/'); } } } @@ -2660,6 +2657,8 @@ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat //UserInfo if ((parts & UriComponents.UserInfo) != 0 && InFact(Flags.HasUserInfo)) { + ReadOnlySpan slice = str.AsSpan(_info.Offset.User, _info.Offset.Host - _info.Offset.User); + if ((nonCanonical & (ushort)UriComponents.UserInfo) != 0) { switch (formatAs) @@ -2667,10 +2666,7 @@ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat case UriFormat.UriEscaped: if (NotAny(Flags.UserEscaped)) { - chars = UriHelper.EscapeString( - _string.AsSpan(_info.Offset.User, _info.Offset.Host - _info.Offset.User), - chars, ref count, - checkExistingEscaped: true, '?', '#'); + UriHelper.EscapeString(slice, ref dest, checkExistingEscaped: true, '?', '#');; } else { @@ -2678,264 +2674,209 @@ private string ReCreateParts(UriComponents parts, ushort nonCanonical, UriFormat { // We should throw here but currently just accept user input known as invalid } - _string.CopyTo(_info.Offset.User, chars, count, _info.Offset.Host - _info.Offset.User); - count += (_info.Offset.Host - _info.Offset.User); + dest.Append(slice); } break; case UriFormat.SafeUnescaped: - chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host - 1, - chars, ref count, '@', '/', '\\', InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : - UnescapeMode.EscapeUnescape, _syntax, false); - chars[count++] = '@'; + UriHelper.UnescapeString(slice[..^1], + ref dest, '@', '/', '\\', + InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape, + _syntax, isQuery: false); + dest.Append('@'); break; case UriFormat.Unescaped: - chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars, - ref count, c_DummyChar, c_DummyChar, c_DummyChar, - UnescapeMode.Unescape | UnescapeMode.UnescapeAll, _syntax, false); + UriHelper.UnescapeString(slice, + ref dest, c_DummyChar, c_DummyChar, c_DummyChar, + UnescapeMode.Unescape | UnescapeMode.UnescapeAll, + _syntax, isQuery: false); break; default: //V1ToStringUnescape - chars = UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars, - ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, - false); + dest.Append(slice); break; } } else { - UriHelper.UnescapeString(_string, _info.Offset.User, _info.Offset.Host, chars, ref count, - c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, false); + dest.Append(slice); } + if (parts == UriComponents.UserInfo) { //strip '@' delimiter - --count; + dest.Length--; } } // Host - if ((parts & UriComponents.Host) != 0 && stemp.Length != 0) + if ((parts & UriComponents.Host) != 0) { - UnescapeMode mode; - if (formatAs != UriFormat.UriEscaped && HostType == Flags.BasicHostType - && (nonCanonical & (ushort)UriComponents.Host) != 0) - { - // only Basic host could be in the escaped form - mode = formatAs == UriFormat.Unescaped - ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll) : - (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape); - } - else - { - mode = UnescapeMode.CopyOnly; - } + string host = _info.Host!; - // NormalizedHost - if ((parts & UriComponents.NormalizedHost) != 0) + if (host.Length != 0) { - stemp = UriHelper.StripBidiControlCharacters(stemp, stemp); - - var hostBuilder = new ValueStringBuilder(stackalloc char[512]); - - // The host may be invalid punycode (www.xn--?-pck.com), - // but we shouldn't throw after the constructor. - if (DomainNameHelper.TryGetUnicodeEquivalent(stemp, ref hostBuilder)) + UnescapeMode mode; + if (formatAs != UriFormat.UriEscaped && HostType == Flags.BasicHostType + && (nonCanonical & (ushort)UriComponents.Host) != 0) { - stemp = hostBuilder.ToString(); + // only Basic host could be in the escaped form + mode = formatAs == UriFormat.Unescaped + ? (UnescapeMode.Unescape | UnescapeMode.UnescapeAll) : + (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape); } else { - hostBuilder.Dispose(); + mode = UnescapeMode.CopyOnly; } - } - - chars = UriHelper.UnescapeString(stemp, 0, stemp.Length, chars, ref count, '/', '?', '#', mode, - _syntax, false); - // A fix up only for SerializationInfo and IpV6 host with a scopeID - if ((parts & UriComponents.SerializationInfoString) != 0 && HostType == Flags.IPv6HostType && - _info.ScopeId is not null) - { - _info.ScopeId.CopyTo(0, chars, count - 1, _info.ScopeId.Length); - count += _info.ScopeId.Length; - chars[count - 1] = ']'; - } - } + var hostBuilder = new ValueStringBuilder(stackalloc char[StackallocThreshold]); - //Port (always wants a ':' delimiter if got to this method) - if ((parts & UriComponents.Port) != 0) - { - if ((nonCanonical & (ushort)UriComponents.Port) == 0) - { - //take it from _string - if (InFact(Flags.NotDefaultPort)) + // NormalizedHost + if ((parts & UriComponents.NormalizedHost) != 0) { - int start = _info.Offset.Path; - while (_string[--start] != ':') + host = UriHelper.StripBidiControlCharacters(host, host); + + // Upconvert any punycode to unicode, xn--pck -> ? + if (!DomainNameHelper.TryGetUnicodeEquivalent(host, ref hostBuilder)) { - ; + hostBuilder.Length = 0; } - _string.CopyTo(start, chars, count, _info.Offset.Path - start); - count += (_info.Offset.Path - start); } - else if ((parts & UriComponents.StrongPort) != 0 && _syntax.DefaultPort != UriParser.NoDefaultPort) + + UriHelper.UnescapeString(hostBuilder.Length == 0 ? host : hostBuilder.AsSpan(), + ref dest, '/', '?', '#', + mode, + _syntax, isQuery: false); + + hostBuilder.Dispose(); + + // A fix up only for SerializationInfo and IpV6 host with a scopeID + if ((parts & UriComponents.SerializationInfoString) != 0 && HostType == Flags.IPv6HostType && _info.ScopeId != null) { - chars[count++] = ':'; - stemp = _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture); - stemp.CopyTo(0, chars, count, stemp.Length); - count += stemp.Length; + dest.Length--; + dest.Append(_info.ScopeId); + dest.Append(']'); } } - else if (InFact(Flags.NotDefaultPort) || ((parts & UriComponents.StrongPort) != 0 && - _syntax.DefaultPort != UriParser.NoDefaultPort)) - { - // recreate string from port value - chars[count++] = ':'; - stemp = _info.Offset.PortValue.ToString(CultureInfo.InvariantCulture); - stemp.CopyTo(0, chars, count, stemp.Length); - count += stemp.Length; - } } - int delimiterAwareIndex; + //Port (always wants a ':' delimiter if got to this method) + if ((parts & UriComponents.Port) != 0 && + (InFact(Flags.NotDefaultPort) || ((parts & UriComponents.StrongPort) != 0 && _syntax.DefaultPort != UriParser.NoDefaultPort))) + { + dest.Append(':'); + + const int MaxUshortLength = 5; + bool success = _info.Offset.PortValue.TryFormat(dest.AppendSpan(MaxUshortLength), out int charsWritten); + Debug.Assert(success); + dest.Length -= MaxUshortLength - charsWritten; + } //Path if ((parts & UriComponents.Path) != 0) { - chars = GetCanonicalPath(chars, ref count, formatAs); + GetCanonicalPath(ref dest, formatAs); // (possibly strip the leading '/' delimiter) if (parts == UriComponents.Path) { - if (InFact(Flags.AuthorityFound) && count != 0 && chars[0] == '/') + int offset; + if (InFact(Flags.AuthorityFound) && dest.Length != 0 && dest[0] == '/') { - delimiterAwareIndex = 1; --count; + offset = 1; } else { - delimiterAwareIndex = 0; + offset = 0; } - return count == 0 ? string.Empty : new string(chars, delimiterAwareIndex, count); + + string result = dest.AsSpan(offset).ToString(); + dest.Dispose(); + return result; } } //Query (possibly strip the '?' delimiter) if ((parts & UriComponents.Query) != 0 && _info.Offset.Query < _info.Offset.Fragment) { - delimiterAwareIndex = (_info.Offset.Query + 1); + int offset = (_info.Offset.Query + 1); if (parts != UriComponents.Query) - chars[count++] = '?'; //see Fragment+1 below + dest.Append('?'); + + UnescapeMode mode = UnescapeMode.CopyOnly; if ((nonCanonical & (ushort)UriComponents.Query) != 0) { - switch (formatAs) + if (formatAs == UriFormat.UriEscaped) { - case UriFormat.UriEscaped: - //Can Assert IsImplicitfile == false - if (NotAny(Flags.UserEscaped)) - { - chars = UriHelper.EscapeString( - _string.AsSpan(delimiterAwareIndex, _info.Offset.Fragment - delimiterAwareIndex), - chars, ref count, - checkExistingEscaped: true, '#'); - } - else - { - UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, - ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, - true); - } - break; - - case V1ToStringUnescape: - - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, - ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ? - UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag, - _syntax, true); - break; - - case UriFormat.Unescaped: - - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, - ref count, '#', c_DummyChar, c_DummyChar, - (UnescapeMode.Unescape | UnescapeMode.UnescapeAll), _syntax, true); - break; - - default: // UriFormat.SafeUnescaped + if (NotAny(Flags.UserEscaped)) + { + UriHelper.EscapeString( + str.AsSpan(offset, _info.Offset.Fragment - offset), + ref dest, checkExistingEscaped: true, '#'); - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, - ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ? - UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), _syntax, true); - break; + goto AfterQuery; + } + } + else + { + mode = formatAs switch + { + V1ToStringUnescape => (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag, + UriFormat.Unescaped => UnescapeMode.Unescape | UnescapeMode.UnescapeAll, + _ => InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape + }; } } - else - { - UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.Fragment, chars, ref count, - c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, true); - } + + UriHelper.UnescapeString(str, offset, _info.Offset.Fragment, + ref dest, '#', c_DummyChar, c_DummyChar, + mode, _syntax, isQuery: true); } + AfterQuery: //Fragment (possibly strip the '#' delimiter) if ((parts & UriComponents.Fragment) != 0 && _info.Offset.Fragment < _info.Offset.End) { - delimiterAwareIndex = _info.Offset.Fragment + 1; + int offset = _info.Offset.Fragment + 1; if (parts != UriComponents.Fragment) - chars[count++] = '#'; //see Fragment+1 below + dest.Append('#'); + + UnescapeMode mode = UnescapeMode.CopyOnly; if ((nonCanonical & (ushort)UriComponents.Fragment) != 0) { - switch (formatAs) + if (formatAs == UriFormat.UriEscaped) { - case UriFormat.UriEscaped: - if (NotAny(Flags.UserEscaped)) - { - chars = UriHelper.EscapeString( - _string.AsSpan(delimiterAwareIndex, _info.Offset.End - delimiterAwareIndex), - chars, ref count, - checkExistingEscaped: true); - } - else - { - UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, - ref count, c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, - false); - } - break; - - case V1ToStringUnescape: - - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, - ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ? - UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag, - _syntax, false); - break; - case UriFormat.Unescaped: - - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, - ref count, '#', c_DummyChar, c_DummyChar, - UnescapeMode.Unescape | UnescapeMode.UnescapeAll, _syntax, false); - break; - - default: // UriFormat.SafeUnescaped + if (NotAny(Flags.UserEscaped)) + { + UriHelper.EscapeString( + str.AsSpan(offset, _info.Offset.End - offset), + ref dest, checkExistingEscaped: true); - chars = UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, - ref count, '#', c_DummyChar, c_DummyChar, (InFact(Flags.UserEscaped) ? - UnescapeMode.Unescape : UnescapeMode.EscapeUnescape), _syntax, false); - break; + goto AfterFragment; + } + } + else + { + mode = formatAs switch + { + V1ToStringUnescape => (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) | UnescapeMode.V1ToStringFlag, + UriFormat.Unescaped => UnescapeMode.Unescape | UnescapeMode.UnescapeAll, + _ => InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape + }; } } - else - { - UriHelper.UnescapeString(_string, delimiterAwareIndex, _info.Offset.End, chars, ref count, - c_DummyChar, c_DummyChar, c_DummyChar, UnescapeMode.CopyOnly, _syntax, false); - } + + UriHelper.UnescapeString(str, offset, _info.Offset.End, + ref dest, '#', c_DummyChar, c_DummyChar, + mode, _syntax, isQuery: false); } + AfterFragment: - return new string(chars, 0, count); + return dest.ToString(); } // @@ -4438,15 +4379,15 @@ private unsafe Check CheckCanonical(char* str, ref int idx, int end, char delim) // the passed array must be long enough to hold at least // canonical unescaped path representation (allocated by the caller) // - private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat formatAs) + private unsafe void GetCanonicalPath(ref ValueStringBuilder dest, UriFormat formatAs) { if (InFact(Flags.FirstSlashAbsent)) - dest[pos++] = '/'; + dest.Append('/'); if (_info.Offset.Path == _info.Offset.Query) - return dest; + return; - int end = pos; + int start = dest.Length; int dosPathIdx = SecuredPathIndex; @@ -4457,8 +4398,7 @@ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat forma { if (InFact(Flags.ShouldBeCompressed)) { - _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path); - end += (_info.Offset.Query - _info.Offset.Path); + dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path)); // If the path was found as needed compression and contains escaped characters, unescape only // interesting characters (safe) @@ -4468,8 +4408,10 @@ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat forma { fixed (char* pdest = dest) { - UnescapeOnly(pdest, pos, ref end, '.', '/', + int end = dest.Length; + UnescapeOnly(pdest, start, ref end, '.', '/', _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar); + dest.Length = end; } } } @@ -4487,29 +4429,37 @@ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat forma str = str.Insert(dosPathIdx + _info.Offset.Path - 1, ":"); } - dest = UriHelper.EscapeString( + UriHelper.EscapeString( str.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path), - dest, ref end, - checkExistingEscaped: !IsImplicitFile, '?', '#'); + ref dest, checkExistingEscaped: !IsImplicitFile, '?', '#'); } else { - _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path); - end += (_info.Offset.Query - _info.Offset.Path); + dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path)); } } // On Unix, escape '\\' in path of file uris to '%5C' canonical form. if (!IsWindowsSystem && InFact(Flags.BackslashInPath) && _syntax.NotAny(UriSyntaxFlags.ConvertPathSlashes) && _syntax.InFact(UriSyntaxFlags.FileLikeUri) && !IsImplicitFile) { - dest = UriHelper.EscapeString(new string(dest, pos, end - pos), dest, ref pos, checkExistingEscaped: true, '\\'); - end = pos; + // We can't do an in-place escape, create a copy + var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]); + copy.Append(dest.AsSpan(start, dest.Length - start)); + + dest.Length = start; + + // CS8350 & CS8352: We can't pass `copy` and `dest` as arguments together as that could leak the scope of the above stackalloc + // As a workaround, re-create the Span in a way that avoids analysis + ReadOnlySpan copySpan = MemoryMarshal.CreateReadOnlySpan(ref copy.GetPinnableReference(), copy.Length); + UriHelper.EscapeString(copySpan, ref dest, checkExistingEscaped: true, '\\'); + start = dest.Length; + + copy.Dispose(); } } else { - _string.CopyTo(_info.Offset.Path, dest, end, _info.Offset.Query - _info.Offset.Path); - end += (_info.Offset.Query - _info.Offset.Path); + dest.Append(_string.AsSpan(_info.Offset.Path, _info.Offset.Query - _info.Offset.Path)); if (InFact(Flags.ShouldBeCompressed)) { @@ -4521,8 +4471,10 @@ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat forma { fixed (char* pdest = dest) { - UnescapeOnly(pdest, pos, ref end, '.', '/', + int end = dest.Length; + UnescapeOnly(pdest, start, ref end, '.', '/', _syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) ? '\\' : c_DummyChar); + dest.Length = end; } } } @@ -4536,77 +4488,82 @@ private unsafe char[] GetCanonicalPath(char[] dest, ref int pos, UriFormat forma // // (path is already >= 3 chars if recognized as a DOS-like) // - if (dosPathIdx != 0 && dest[dosPathIdx + pos - 1] == '|') - dest[dosPathIdx + pos - 1] = ':'; + int offset = start + dosPathIdx; + if (dosPathIdx != 0 && dest[offset - 1] == '|') + dest[offset - 1] = ':'; - if (InFact(Flags.ShouldBeCompressed)) + if (InFact(Flags.ShouldBeCompressed) && dest.Length - offset > 0) { // It will also convert back slashes if needed - dest = Compress(dest, (ushort)(pos + dosPathIdx), ref end, _syntax); - if (dest[pos] == '\\') - dest[pos] = '/'; + dest.Length = offset + Compress(dest.RawChars.Slice(offset, dest.Length - offset), _syntax); + if (dest[start] == '\\') + dest[start] = '/'; // Escape path if requested and found as not fully escaped if (formatAs == UriFormat.UriEscaped && NotAny(Flags.UserEscaped) && InFact(Flags.E_PathNotCanonical)) { //Note: Flags.UserEscaped check is solely based on trusting the user - dest = UriHelper.EscapeString(new string(dest, pos, end - pos), dest, ref pos, checkExistingEscaped: !IsImplicitFile, '?', '#'); - end = pos; + + // We can't do an in-place escape, create a copy + var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]); + copy.Append(dest.AsSpan(start, dest.Length - start)); + + dest.Length = start; + + // CS8350 & CS8352: We can't pass `copy` and `dest` as arguments together as that could leak the scope of the above stackalloc + // As a workaround, re-create the Span in a way that avoids analysis + ReadOnlySpan copySpan = MemoryMarshal.CreateReadOnlySpan(ref copy.GetPinnableReference(), copy.Length); + UriHelper.EscapeString(copySpan, ref dest, checkExistingEscaped: !IsImplicitFile, '?', '#'); + start = dest.Length; + + copy.Dispose(); } } - else if (_syntax.InFact(UriSyntaxFlags.ConvertPathSlashes) && InFact(Flags.BackslashInPath)) - { - for (int i = pos; i < end; ++i) - if (dest[i] == '\\') dest[i] = '/'; - } if (formatAs != UriFormat.UriEscaped && InFact(Flags.PathNotCanonical)) { UnescapeMode mode; - if (InFact(Flags.PathNotCanonical)) + switch (formatAs) { - switch (formatAs) - { - case V1ToStringUnescape: + case V1ToStringUnescape: - mode = (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) - | UnescapeMode.V1ToStringFlag; - if (IsImplicitFile) - mode &= ~UnescapeMode.Unescape; - break; + mode = (InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape) + | UnescapeMode.V1ToStringFlag; + if (IsImplicitFile) + mode &= ~UnescapeMode.Unescape; + break; - case UriFormat.Unescaped: - mode = IsImplicitFile ? UnescapeMode.CopyOnly - : UnescapeMode.Unescape | UnescapeMode.UnescapeAll; - break; + case UriFormat.Unescaped: + mode = IsImplicitFile ? UnescapeMode.CopyOnly + : UnescapeMode.Unescape | UnescapeMode.UnescapeAll; + break; - default: // UriFormat.SafeUnescaped + default: // UriFormat.SafeUnescaped - mode = InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape; - if (IsImplicitFile) - mode &= ~UnescapeMode.Unescape; - break; - } - } - else - { - mode = UnescapeMode.CopyOnly; + mode = InFact(Flags.UserEscaped) ? UnescapeMode.Unescape : UnescapeMode.EscapeUnescape; + if (IsImplicitFile) + mode &= ~UnescapeMode.Unescape; + break; } - char[] dest1 = new char[dest.Length]; - Buffer.BlockCopy(dest, 0, dest1, 0, end * sizeof(char)); - fixed (char* pdest = dest1) + if (mode != UnescapeMode.CopyOnly) { - dest = UriHelper.UnescapeString(pdest, pos, end, dest, ref pos, '?', '#', c_DummyChar, mode, - _syntax, false); + // We can't do an in-place unescape, create a copy + var copy = new ValueStringBuilder(stackalloc char[StackallocThreshold]); + copy.Append(dest.AsSpan(start, dest.Length - start)); + + dest.Length = start; + fixed (char* pCopy = copy) + { + UriHelper.UnescapeString(pCopy, 0, copy.Length, + ref dest, '?', '#', c_DummyChar, + mode, + _syntax, isQuery: false); + } + + copy.Dispose(); } } - else - { - pos = end; - } - - return dest; } // works only with ASCII characters, used to partially unescape path before compressing @@ -4677,147 +4634,132 @@ private static unsafe void UnescapeOnly(char* pch, int start, ref int end, char end -= (int)(pch - pnew); } + private static void Compress(char[] dest, int start, ref int destLength, UriParser syntax) + { + destLength = start + Compress(dest.AsSpan(start, destLength - start), syntax); + } + // // This will compress any "\" "/../" "/./" "///" "/..../" /XXX.../, etc found in the input // // The passed syntax controls whether to use aggressive compression or the one specified in RFC 2396 // - private static char[] Compress(char[] dest, int start, ref int destLength, UriParser syntax) + private static int Compress(Span span, UriParser syntax) { - ushort slashCount = 0; - ushort lastSlash = 0; - ushort dotCount = 0; - ushort removeSegments = 0; + int slashCount = 0; + int lastSlash = 0; + int dotCount = 0; + int removeSegments = 0; - unchecked + for (int i = span.Length - 1; i >= 0; i--) { - //ushort i == -1 and start == -1 overflow is ok here - ushort i = (ushort)((ushort)destLength - (ushort)1); - start = (ushort)(start - 1); + char ch = span[i]; + if (ch == '\\' && syntax.InFact(UriSyntaxFlags.ConvertPathSlashes)) + { + span[i] = ch = '/'; + } - for (; i != start; --i) + // compress multiple '/' for file URI + if (ch == '/') { - char ch = dest[i]; - if (ch == '\\' && syntax.InFact(UriSyntaxFlags.ConvertPathSlashes)) + ++slashCount; + } + else + { + if (slashCount > 1) { - dest[i] = ch = '/'; + // else preserve repeated slashes + lastSlash = i + 1; } + slashCount = 0; + } - // - // compress multiple '/' for file URI - // - if (ch == '/') - { - ++slashCount; - } - else + if (ch == '.') + { + ++dotCount; + continue; + } + else if (dotCount != 0) + { + bool skipSegment = syntax.NotAny(UriSyntaxFlags.CanonicalizeAsFilePath) + && (dotCount > 2 || ch != '/'); + + // Cases: + // /./ = remove this segment + // /../ = remove this segment, mark next for removal + // /....x = DO NOT TOUCH, leave as is + // x.../ = DO NOT TOUCH, leave as is, except for V2 legacy mode + if (!skipSegment && ch == '/') { - if (slashCount > 1) + if ((lastSlash == i + dotCount + 1 // "/..../" + || (lastSlash == 0 && i + dotCount + 1 == span.Length)) // "/..." + && (dotCount <= 2)) { - // else preserve repeated slashes - lastSlash = (ushort)(i + 1); - } - slashCount = 0; - } + // /./ or /. or /../ or /.. - if (ch == '.') - { - ++dotCount; - continue; - } - else if (dotCount != 0) - { - bool skipSegment = syntax.NotAny(UriSyntaxFlags.CanonicalizeAsFilePath) - && (dotCount > 2 || ch != '/' || i == start); + // span.Remove(i + 1, dotCount + (lastSlash == 0 ? 0 : 1)); + lastSlash = i + 1 + dotCount + (lastSlash == 0 ? 0 : 1); + span.Slice(lastSlash).CopyTo(span.Slice(i + 1)); + span = span.Slice(0, span.Length - (lastSlash - i - 1)); - // - // Cases: - // /./ = remove this segment - // /../ = remove this segment, mark next for removal - // /....x = DO NOT TOUCH, leave as is - // x.../ = DO NOT TOUCH, leave as is, except for V2 legacy mode - // - if (!skipSegment && ch == '/') - { - if ((lastSlash == i + dotCount + 1 // "/..../" - || (lastSlash == 0 && i + dotCount + 1 == destLength)) // "/..." - && (dotCount <= 2)) + lastSlash = i; + if (dotCount == 2) { - // - // /./ or /. or /../ or /.. - // - // just reusing a variable slot we perform //dest.Remove(i+1, dotCount + (lastSlash==0?0:1)); - lastSlash = (ushort)(i + 1 + dotCount + (lastSlash == 0 ? 0 : 1)); - Buffer.BlockCopy(dest, lastSlash * sizeof(char), dest, (i + 1) * sizeof(char), (destLength - lastSlash) * sizeof(char)); - destLength -= (lastSlash - i - 1); - - lastSlash = i; - if (dotCount == 2) - { - // - // We have 2 dots in between like /../ or /.., - // Mark next segment for removal and remove this /../ or /.. - // - ++removeSegments; - } - dotCount = 0; - continue; + // We have 2 dots in between like /../ or /.., + // Mark next segment for removal and remove this /../ or /.. + ++removeSegments; } + dotCount = 0; + continue; } - // .NET 4.5 no longer removes trailing dots in a path segment x.../ or x... - dotCount = 0; - - // - // Here all other cases go such as - // x.[..]y or /.[..]x or (/x.[...][/] && removeSegments !=0) } + // .NET 4.5 no longer removes trailing dots in a path segment x.../ or x... + dotCount = 0; - // - // Now we may want to remove a segment because of previous /../ - // - if (ch == '/') + // Here all other cases go such as + // x.[..]y or /.[..]x or (/x.[...][/] && removeSegments !=0) + } + + // Now we may want to remove a segment because of previous /../ + if (ch == '/') + { + if (removeSegments != 0) { - if (removeSegments != 0) - { - --removeSegments; + --removeSegments; - // just reusing a variable slot we perform //dest.Remove(i+1, lastSlash - i); - lastSlash = (ushort)(lastSlash + 1); - Buffer.BlockCopy(dest, lastSlash * sizeof(char), dest, (i + 1) * sizeof(char), (destLength - lastSlash) * sizeof(char)); - destLength -= (lastSlash - i - 1); - } - lastSlash = i; + span.Slice(lastSlash + 1).CopyTo(span.Slice(i + 1)); + span = span.Slice(0, span.Length - (lastSlash - i)); } + lastSlash = i; } + } - start = (ushort)((ushort)start + (ushort)1); - } //end of unchecked - - if ((ushort)destLength > start && syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath)) + if (span.Length != 0 && syntax.InFact(UriSyntaxFlags.CanonicalizeAsFilePath)) { if (slashCount <= 1) { - if (removeSegments != 0 && dest[start] != '/') + if (removeSegments != 0 && span[0] != '/') { //remove first not rooted segment - lastSlash = (ushort)(lastSlash + 1); - Buffer.BlockCopy(dest, lastSlash * sizeof(char), dest, start * sizeof(char), (destLength - lastSlash) * sizeof(char)); - destLength -= lastSlash; + lastSlash++; + span.Slice(lastSlash).CopyTo(span); + return span.Length - lastSlash; } else if (dotCount != 0) { // If final string starts with a segment looking like .[...]/ or .[...] // then we remove this first segment - if (lastSlash == dotCount + 1 || (lastSlash == 0 && dotCount + 1 == destLength)) + if (lastSlash == dotCount || (lastSlash == 0 && dotCount == span.Length)) { - dotCount = (ushort)(dotCount + (lastSlash == 0 ? 0 : 1)); - Buffer.BlockCopy(dest, dotCount * sizeof(char), dest, start * sizeof(char), (destLength - dotCount) * sizeof(char)); - destLength -= dotCount; + dotCount += lastSlash == 0 ? 0 : 1; + span.Slice(dotCount).CopyTo(span); + return span.Length - dotCount; } } } } - return dest; + + return span.Length; } // @@ -5022,7 +4964,7 @@ private static string CombineUri(Uri basePart, string relativePart, UriFormat ur if (basePart.IsDosPath) { // The FILE DOS path comes as /c:/path, we have to exclude first 3 chars from compression - path = Compress(path, 3, ref length, basePart.Syntax); + Compress(path, 3, ref length, basePart.Syntax); return string.Concat(path.AsSpan(1, length - 1), extra); } else if (!IsWindowsSystem && basePart.IsUnixPath) @@ -5040,7 +4982,7 @@ private static string CombineUri(Uri basePart, string relativePart, UriFormat ur } } //compress the path - path = Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax); + Compress(path, basePart.SecuredPathIndex, ref length, basePart.Syntax); return string.Concat(left, path.AsSpan(0, length), extra); } diff --git a/src/libraries/System.Private.Uri/src/System/UriExt.cs b/src/libraries/System.Private.Uri/src/System/UriExt.cs index 66552d086da08..ca43d833a79de 100644 --- a/src/libraries/System.Private.Uri/src/System/UriExt.cs +++ b/src/libraries/System.Private.Uri/src/System/UriExt.cs @@ -534,7 +534,7 @@ public static string UnescapeDataString(string stringToUnescape) if (position == -1) return stringToUnescape; - var vsb = new ValueStringBuilder(stackalloc char[256]); + var vsb = new ValueStringBuilder(stackalloc char[StackallocThreshold]); vsb.EnsureCapacity(stringToUnescape.Length); vsb.Append(stringToUnescape.AsSpan(0, position)); diff --git a/src/libraries/System.Private.Uri/src/System/UriHelper.cs b/src/libraries/System.Private.Uri/src/System/UriHelper.cs index 27988b1bda87f..660d15d49ade4 100644 --- a/src/libraries/System.Private.Uri/src/System/UriHelper.cs +++ b/src/libraries/System.Private.Uri/src/System/UriHelper.cs @@ -3,7 +3,6 @@ using System.Text; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Buffers; @@ -153,20 +152,13 @@ internal static string EscapeString( // Otherwise, create a ValueStringBuilder to store the escaped data into, // append to it all of the noEscape chars we already iterated through, // escape the rest, and return the result as a string. - var vsb = new ValueStringBuilder(stackalloc char[256]); + var vsb = new ValueStringBuilder(stackalloc char[Uri.StackallocThreshold]); vsb.Append(stringToEscape.AsSpan(0, i)); EscapeStringToBuilder(stringToEscape.AsSpan(i), ref vsb, noEscape, checkExistingEscaped); return vsb.ToString(); } - // forceX characters are always escaped if found - // destPos - starting offset in dest for output, on return this will be an exclusive "end" in the output. - // In case "dest" has lack of space it will be reallocated by preserving the _whole_ content up to current destPos - // Returns null if nothing has to be escaped AND passed dest was null, otherwise the resulting array with the updated destPos - [return: NotNullIfNotNull("dest")] - internal static char[]? EscapeString( - ReadOnlySpan stringToEscape, - char[]? dest, ref int destPos, + internal static unsafe void EscapeString(ReadOnlySpan stringToEscape, ref ValueStringBuilder dest, bool checkExistingEscaped, char forceEscape1 = '\0', char forceEscape2 = '\0') { // Get the table of characters that do not need to be escaped. @@ -193,35 +185,17 @@ internal static string EscapeString( for (; i < stringToEscape.Length && (c = stringToEscape[i]) <= 0x7F && noEscape[c]; i++) ; if (i == stringToEscape.Length) { - if (dest != null) - { - EnsureCapacity(dest, destPos, stringToEscape.Length); - stringToEscape.CopyTo(dest.AsSpan(destPos)); - destPos += stringToEscape.Length; - } - - return dest; + dest.Append(stringToEscape); } + else + { + dest.Append(stringToEscape.Slice(0, i)); - // Otherwise, create a ValueStringBuilder to store the escaped data into, - // append to it all of the noEscape chars we already iterated through, and - // escape the rest into the ValueStringBuilder. - var vsb = new ValueStringBuilder(stackalloc char[256]); - vsb.Append(stringToEscape.Slice(0, i)); - EscapeStringToBuilder(stringToEscape.Slice(i), ref vsb, noEscape, checkExistingEscaped); - - // Finally update dest with the result. - EnsureCapacity(dest, destPos, vsb.Length); - vsb.TryCopyTo(dest.AsSpan(destPos), out int charsWritten); - destPos += charsWritten; - return dest; + // CS8350 & CS8352: We can't pass `noEscape` and `dest` as arguments together as that could leak the scope of the above stackalloc + // As a workaround, re-create the Span in a way that avoids analysis + ReadOnlySpan noEscapeCopy = MemoryMarshal.CreateReadOnlySpan(ref MemoryMarshal.GetReference(noEscape), noEscape.Length); - static void EnsureCapacity(char[]? dest, int destSize, int requiredSize) - { - if (dest == null || dest.Length - destSize < requiredSize) - { - Array.Resize(ref dest, destSize + requiredSize + 120); // 120 == arbitrary minimum-empty space copied from previous implementation - } + EscapeStringToBuilder(stringToEscape.Slice(i), ref dest, noEscapeCopy, checkExistingEscaped); } } @@ -342,6 +316,14 @@ internal static unsafe void UnescapeString(string input, int start, int end, ref UnescapeString(pStr, start, end, ref dest, rsvd1, rsvd2, rsvd3, unescapeMode, syntax, isQuery); } } + internal static unsafe void UnescapeString(ReadOnlySpan input, ref ValueStringBuilder dest, + char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser? syntax, bool isQuery) + { + fixed (char* pStr = &MemoryMarshal.GetReference(input)) + { + UnescapeString(pStr, 0, input.Length, ref dest, rsvd1, rsvd2, rsvd3, unescapeMode, syntax, isQuery); + } + } internal static unsafe void UnescapeString(char* pStr, int start, int end, ref ValueStringBuilder dest, char rsvd1, char rsvd2, char rsvd3, UnescapeMode unescapeMode, UriParser? syntax, bool isQuery) { diff --git a/src/libraries/System.Private.Uri/tests/UnitTests/Fakes/FakeUri.cs b/src/libraries/System.Private.Uri/tests/UnitTests/Fakes/FakeUri.cs index ee0a6fe04ac73..e23e65223c4db 100644 --- a/src/libraries/System.Private.Uri/tests/UnitTests/Fakes/FakeUri.cs +++ b/src/libraries/System.Private.Uri/tests/UnitTests/Fakes/FakeUri.cs @@ -7,6 +7,7 @@ public class Uri { internal const char c_DummyChar = (char)0xFFFF; //An Invalid Unicode character used as a dummy char passed into the parameter internal const int c_MaxUriBufferSize = 0xFFF0; + internal const int StackallocThreshold = 512; internal static bool IriParsingStatic(UriParser? syntax) { 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 60d020361dae6..c21a28afd11e9 100644 --- a/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs +++ b/src/libraries/System.Reflection.DispatchProxy/src/System/Reflection/DispatchProxyGenerator.cs @@ -40,9 +40,9 @@ namespace System.Reflection // internal static class DispatchProxyGenerator { - // Generated proxies have a private Action field that all generated methods - // invoke. It is the first field in the class and the first ctor parameter. - private const int InvokeActionFieldAndCtorParameterIndex = 0; + // Generated proxies have a private MethodInfo[] field that generated methods use to get the corresponding MethodInfo. + // It is the first field in the class and the first ctor parameter. + private const int MethodInfosFieldAndCtorParameterIndex = 0; // Proxies are requested for a pair of types: base type and interface type. // The generated proxy will subclass the given base type and implement the interface type. @@ -57,9 +57,11 @@ internal static class DispatchProxyGenerator // This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs, // which would ultimately be a more expensive leak. // Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create. - private static readonly Dictionary> s_baseTypeAndInterfaceToGeneratedProxyType = new Dictionary>(); + private static readonly Dictionary> s_baseTypeAndInterfaceToGeneratedProxyType = new Dictionary>(); private static readonly ProxyAssembly s_proxyAssembly = new ProxyAssembly(); private static readonly MethodInfo s_dispatchProxyInvokeMethod = typeof(DispatchProxy).GetTypeInfo().GetDeclaredMethod("Invoke")!; + private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetRuntimeMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!; + private static readonly MethodInfo s_makeGenericMethodMethod = typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!; // Returns a new instance of a proxy the derives from 'baseType' and implements 'interfaceType' internal static object CreateProxyInstance( @@ -69,24 +71,23 @@ internal static object CreateProxyInstance( Debug.Assert(baseType != null); Debug.Assert(interfaceType != null); - Type proxiedType = GetProxyType(baseType, interfaceType); - return Activator.CreateInstance(proxiedType, (Action)DispatchProxyGenerator.Invoke)!; + GeneratedTypeInfo proxiedType = GetProxyType(baseType, interfaceType); + return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!; } - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - private static Type GetProxyType( + private static GeneratedTypeInfo GetProxyType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType, Type interfaceType) { lock (s_baseTypeAndInterfaceToGeneratedProxyType) { - if (!s_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary? interfaceToProxy)) + if (!s_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary? interfaceToProxy)) { - interfaceToProxy = new Dictionary(); + interfaceToProxy = new Dictionary(); s_baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy; } - if (!interfaceToProxy.TryGetValue(interfaceType, out Type? generatedProxy)) + if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy)) { generatedProxy = GenerateProxyType(baseType, interfaceType); interfaceToProxy[interfaceType] = generatedProxy; @@ -97,8 +98,7 @@ private static Type GetProxyType( } // Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType' - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - private static Type GenerateProxyType( + private static GeneratedTypeInfo GenerateProxyType( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType, Type interfaceType) { @@ -139,59 +139,23 @@ private static Type GenerateProxyType( pb.AddInterfaceImpl(interfaceType); - Type generatedProxyType = pb.CreateType(); + GeneratedTypeInfo generatedProxyType = pb.CreateType(); return generatedProxyType; } - // All generated proxy methods call this static helper method to dispatch. - // Its job is to unpack the arguments and the 'this' instance and to dispatch directly - // to the (abstract) DispatchProxy.Invoke() method. - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", - Justification = "MakeGenericMethod is safe here because the user code invoking the generic method will reference " + - "the GenericTypes being used, which will guarantee the requirements of the generic method.")] - private static void Invoke(object?[] args) + private class GeneratedTypeInfo { - PackedArgs packed = new PackedArgs(args); - MethodBase method = s_proxyAssembly.ResolveMethodToken(packed.DeclaringType, packed.MethodToken); - if (method.IsGenericMethodDefinition) - method = ((MethodInfo)method).MakeGenericMethod(packed.GenericTypes!); - - // Call (protected method) DispatchProxy.Invoke() - try - { - Debug.Assert(s_dispatchProxyInvokeMethod != null); - object? returnValue = s_dispatchProxyInvokeMethod!.Invoke(packed.DispatchProxy, - new object?[] { method, packed.Args }); - packed.ReturnValue = returnValue; - } - catch (TargetInvocationException tie) + public GeneratedTypeInfo( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type generatedType, + MethodInfo[] methodInfos) { - Debug.Assert(tie.InnerException != null); - ExceptionDispatchInfo.Capture(tie.InnerException).Throw(); + GeneratedType = generatedType; + MethodInfos = methodInfos; } - } - private class PackedArgs - { - internal const int DispatchProxyPosition = 0; - internal const int DeclaringTypePosition = 1; - internal const int MethodTokenPosition = 2; - internal const int ArgsPosition = 3; - internal const int GenericTypesPosition = 4; - internal const int ReturnValuePosition = 5; - - internal static readonly Type[] PackedTypes = new Type[] { typeof(object), typeof(Type), typeof(int), typeof(object[]), typeof(Type[]), typeof(object) }; - - private readonly object?[] _args; - internal PackedArgs() : this(new object[PackedTypes.Length]) { } - internal PackedArgs(object?[] args) { _args = args; } - - internal DispatchProxy? DispatchProxy { get { return (DispatchProxy?)_args[DispatchProxyPosition]; } } - internal Type? DeclaringType { get { return (Type?)_args[DeclaringTypePosition]; } } - internal int MethodToken { get { return (int)_args[MethodTokenPosition]!; } } - internal object[]? Args { get { return (object[]?)_args[ArgsPosition]; } } - internal Type[]? GenericTypes { get { return (Type[]?)_args[GenericTypesPosition]; } } - internal object? ReturnValue { /*get { return args[ReturnValuePosition]; }*/ set { _args[ReturnValuePosition] = value; } } + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] + public Type GeneratedType { get; } + public MethodInfo[] MethodInfos { get; } } private class ProxyAssembly @@ -200,10 +164,6 @@ private class ProxyAssembly private readonly ModuleBuilder _mb; private int _typeId; - // Maintain a MethodBase-->int, int-->MethodBase mapping to permit generated code - // to pass methods by token - private readonly Dictionary _methodToToken = new Dictionary(); - private readonly List _methodsByToken = new List(); private readonly HashSet _ignoresAccessAssemblyNames = new HashSet(); private ConstructorInfo? _ignoresAccessChecksToAttributeConstructor; @@ -268,36 +228,16 @@ internal void EnsureTypeIsVisible(Type type) } } } - - internal void GetTokenForMethod(MethodBase method, out Type type, out int token) - { - Debug.Assert(method.DeclaringType != null); - type = method.DeclaringType!; - token = 0; - if (!_methodToToken.TryGetValue(method, out token)) - { - _methodsByToken.Add(method); - token = _methodsByToken.Count - 1; - _methodToToken[method] = token; - } - } - - internal MethodBase ResolveMethodToken(Type? type, int token) - { - Debug.Assert(token >= 0 && token < _methodsByToken.Count); - return _methodsByToken[token]; - } } private class ProxyBuilder { - private static readonly MethodInfo s_delegateInvoke = typeof(Action).GetMethod("Invoke")!; - private readonly ProxyAssembly _assembly; private readonly TypeBuilder _tb; [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] private readonly Type _proxyBaseType; private readonly List _fields; + private readonly List _methodInfos; internal ProxyBuilder( ProxyAssembly assembly, @@ -309,7 +249,9 @@ internal ProxyBuilder( _proxyBaseType = proxyBaseType; _fields = new List(); - _fields.Add(tb.DefineField("invoke", typeof(Action), FieldAttributes.Private)); + _fields.Add(tb.DefineField("_methodInfos", typeof(MethodInfo[]), FieldAttributes.Private)); + + _methodInfos = new List(); } private void Complete() @@ -341,11 +283,10 @@ private void Complete() il.Emit(OpCodes.Ret); } - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] - internal Type CreateType() + internal GeneratedTypeInfo CreateType() { this.Complete(); - return _tb.CreateType()!; + return new GeneratedTypeInfo(_tb.CreateType()!, _methodInfos.ToArray()); } [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", @@ -388,7 +329,9 @@ internal void AddInterfaceImpl(Type iface) if (!mi.IsVirtual || mi.IsFinal) continue; - MethodBuilder mdb = AddMethodImpl(mi); + int methodInfoIndex = _methodInfos.Count; + _methodInfos.Add(mi); + MethodBuilder mdb = AddMethodImpl(mi, methodInfoIndex); if (propertyMap.TryGetValue(mi, out PropertyAccessorInfo? associatedProperty)) { if (MethodInfoEqualityComparer.Instance.Equals(associatedProperty.InterfaceGetMethod, mi)) @@ -446,7 +389,7 @@ internal void AddInterfaceImpl(Type iface) } } - private MethodBuilder AddMethodImpl(MethodInfo mi) + private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex) { ParameterInfo[] parameters = mi.GetParameters(); Type[] paramTypes = ParamTypes(parameters, false); @@ -487,54 +430,52 @@ private MethodBuilder AddMethodImpl(MethodInfo mi) } } - // object[] packed = new object[PackedArgs.PackedTypes.Length]; - GenericArray packedArr = new GenericArray(il, PackedArgs.PackedTypes.Length); - - // packed[PackedArgs.DispatchProxyPosition] = this; - packedArr.BeginSet(PackedArgs.DispatchProxyPosition); + // MethodInfo methodInfo = _methodInfos[methodInfoIndex]; + LocalBuilder methodInfoLocal = il.DeclareLocal(typeof(MethodInfo)); il.Emit(OpCodes.Ldarg_0); - packedArr.EndSet(typeof(DispatchProxy)); - - // packed[PackedArgs.DeclaringTypePosition] = typeof(iface); - MethodInfo Type_GetTypeFromHandle = typeof(Type).GetRuntimeMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!; - _assembly.GetTokenForMethod(mi, out Type declaringType, out int methodToken); - packedArr.BeginSet(PackedArgs.DeclaringTypePosition); - il.Emit(OpCodes.Ldtoken, declaringType); - il.Emit(OpCodes.Call, Type_GetTypeFromHandle); - packedArr.EndSet(typeof(object)); - - // packed[PackedArgs.MethodTokenPosition] = iface method token; - packedArr.BeginSet(PackedArgs.MethodTokenPosition); - il.Emit(OpCodes.Ldc_I4, methodToken); - packedArr.EndSet(typeof(int)); - - // packed[PackedArgs.ArgsPosition] = args; - packedArr.BeginSet(PackedArgs.ArgsPosition); - argsArr.Load(); - packedArr.EndSet(typeof(object[])); + il.Emit(OpCodes.Ldfld, _fields[MethodInfosFieldAndCtorParameterIndex]); // MethodInfo[] _methodInfos + il.Emit(OpCodes.Ldc_I4, methodInfoIndex); + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stloc, methodInfoLocal); - // packed[PackedArgs.GenericTypesPosition] = mi.GetGenericArguments(); if (mi.ContainsGenericParameters) { - packedArr.BeginSet(PackedArgs.GenericTypesPosition); + // methodInfo = methodInfo.MakeGenericMethod(mi.GetGenericArguments()); + il.Emit(OpCodes.Ldloc, methodInfoLocal); + Type[] genericTypes = mi.GetGenericArguments(); GenericArray typeArr = new GenericArray(il, genericTypes.Length); for (int i = 0; i < genericTypes.Length; ++i) { typeArr.BeginSet(i); il.Emit(OpCodes.Ldtoken, genericTypes[i]); - il.Emit(OpCodes.Call, Type_GetTypeFromHandle); + il.Emit(OpCodes.Call, s_getTypeFromHandleMethod); typeArr.EndSet(typeof(Type)); } typeArr.Load(); - packedArr.EndSet(typeof(Type[])); + + il.Emit(OpCodes.Callvirt, s_makeGenericMethodMethod); + il.Emit(OpCodes.Stloc, methodInfoLocal); } - // Call static DispatchProxyHelper.Invoke(object[]) + // object result = this.Invoke(methodInfo, args); + LocalBuilder? resultLocal = mi.ReturnType != typeof(void) ? + il.DeclareLocal(typeof(object)) : + null; il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, _fields[InvokeActionFieldAndCtorParameterIndex]); // delegate - packedArr.Load(); - il.Emit(OpCodes.Call, s_delegateInvoke); + il.Emit(OpCodes.Ldloc, methodInfoLocal); + argsArr.Load(); + il.Emit(OpCodes.Callvirt, s_dispatchProxyInvokeMethod); + + if (resultLocal != null) + { + il.Emit(OpCodes.Stloc, resultLocal); + } + else + { + // drop the result for void methods + il.Emit(OpCodes.Pop); + } for (int i = 0; i < parameters.Length; i++) { @@ -546,9 +487,10 @@ private MethodBuilder AddMethodImpl(MethodInfo mi) } } - if (mi.ReturnType != typeof(void)) + if (resultLocal != null) { - packedArr.Get(PackedArgs.ReturnValuePosition); + // return (mi.ReturnType)result; + il.Emit(OpCodes.Ldloc, resultLocal); Convert(il, typeof(object), mi.ReturnType, false); } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs index aea831ffc4063..3b0986a46e414 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Convert.ToHexString.cs @@ -1,5 +1,9 @@ -using System.Text; +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + using Xunit; +using System.Text; +using System.Collections.Generic; namespace System.Tests { @@ -62,5 +66,45 @@ public static unsafe void InputTooLarge() { AssertExtensions.Throws("bytes", () => Convert.ToHexString(new ReadOnlySpan((void*)0, Int32.MaxValue))); } + + public static IEnumerable ToHexStringTestData() + { + yield return new object[] { new byte[0], "" }; + yield return new object[] { new byte[] { 0x00 }, "00" }; + yield return new object[] { new byte[] { 0x01 }, "01" }; + yield return new object[] { new byte[] { 0xFF }, "FF" }; + yield return new object[] { new byte[] { 0x00, 0x00 }, "0000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD }, "ABCD" }; + yield return new object[] { new byte[] { 0xFF, 0xFF }, "FFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00 }, "000000" }; + yield return new object[] { new byte[] { 0x01, 0x02, 0x03 }, "010203" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF }, "FFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00 }, "00000000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12 }, "ABCDEF12" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34 }, "ABCDEF1234" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 }, "ABCDEF123456" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "00000000000000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78 }, "ABCDEF12345678" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "0000000000000000" }; + yield return new object[] { new byte[] { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56, 0x78, 0x90 }, "ABCDEF1234567890" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFF" }; + yield return new object[] { new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, "000000000000000000" }; + yield return new object[] { new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }, "010203040506070809" }; + yield return new object[] { new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, "FFFFFFFFFFFFFFFFFF" }; + } + + [Theory] + [MemberData(nameof(ToHexStringTestData))] + public static unsafe void ToHexString(byte[] input, string expected) + { + string actual = Convert.ToHexString(input); + Assert.Equal(expected, actual); + } } } diff --git a/src/libraries/System.Runtime.Extensions/tests/System/Random.cs b/src/libraries/System.Runtime.Extensions/tests/System/Random.cs index 46e36a786e5dd..56318e7b34d32 100644 --- a/src/libraries/System.Runtime.Extensions/tests/System/Random.cs +++ b/src/libraries/System.Runtime.Extensions/tests/System/Random.cs @@ -1,26 +1,78 @@ // 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.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; using Xunit; namespace System.Tests { - public static class RandomTests + public class RandomTests { - [Fact] - public static void Unseeded() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void InvalidArguments_Throws(bool derived, bool seeded) { - Random r = new Random(); + Random r = Create(derived, seeded); + Assert.Throws(() => r.NextBytes(null)); + Assert.Throws(() => r.Next(-1)); + Assert.Throws(() => r.Next(2, 1)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void SmallRanges_ReturnsExpectedValue(bool derived, bool seeded) + { + Random r = Create(derived, seeded); + + Assert.Equal(0, r.Next(0)); + Assert.Equal(0, r.Next(0, 0)); + Assert.Equal(1, r.Next(1, 1)); + + Assert.Equal(0, r.Next(1)); + Assert.Equal(1, r.Next(1, 2)); + + Assert.Equal(0, r.NextInt64(0)); + Assert.Equal(0, r.NextInt64(0, 0)); + Assert.Equal(1, r.NextInt64(1, 1)); + + Assert.Equal(0, r.NextInt64(1)); + Assert.Equal(1, r.NextInt64(1, 2)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void NextInt_AllValuesAreWithinSpecifiedRange(bool derived, bool seeded) + { + Random r = Create(derived, seeded); + for (int i = 0; i < 1000; i++) { - int x = r.Next(20); - Assert.True(x >= 0 && x < 20); + Assert.InRange(r.Next(20), 0, 19); + Assert.InRange(r.Next(20, 30), 20, 29); + + Assert.InRange(r.NextInt64(20), 0, 19); + Assert.InRange(r.NextInt64(20, 30), 20, 29); } + for (int i = 0; i < 1000; i++) { - int x = r.Next(20, 30); - Assert.True(x >= 20 && x < 30); + float x = r.NextSingle(); + Assert.True(x >= 0.0 && x < 1.0); } + for (int i = 0; i < 1000; i++) { double x = r.NextDouble(); @@ -28,68 +80,168 @@ public static void Unseeded() } } - [Fact] - public static void Seeded() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Next_Int_AllValuesWithinSmallRangeHit(bool derived, bool seeded) { - int seed = Environment.TickCount; + Random r = Create(derived, seeded); - Random r1 = new Random(seed); - Random r2 = new Random(seed); + var hs = new HashSet(); + for (int i = 0; i < 10_000; i++) + { + hs.Add(r.Next(4)); + } + + for (int i = 0; i < 4; i++) + { + Assert.Contains(i, hs); + } + + Assert.DoesNotContain(-1, hs); + Assert.DoesNotContain(4, hs); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Next_IntInt_AllValuesWithinSmallRangeHit(bool derived, bool seeded) + { + Random r = Create(derived, seeded); - byte[] b1 = new byte[1000]; - r1.NextBytes(b1); - byte[] b2 = new byte[1000]; - r2.NextBytes(b2); - for (int i = 0; i < b1.Length; i++) + var hs = new HashSet(); + for (int i = 0; i < 10_000; i++) { - Assert.Equal(b1[i], b2[i]); + hs.Add(r.Next(42, 44)); } - for (int i = 0; i < b1.Length; i++) + + for (int i = 42; i < 44; i++) { - int x1 = r1.Next(); - int x2 = r2.Next(); - Assert.Equal(x1, x2); + Assert.Contains(i, hs); } + + Assert.DoesNotContain(41, hs); + Assert.DoesNotContain(44, hs); } - // Random has a predictable sequence of values it generates based on its seed. - // So that we'll be made aware if a change to the implementation causes these - // sequences to change, this test verifies the first few numbers for a few seeds. - private static int[][] Values() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Next_Long_AllValuesWithinSmallRangeHit(bool derived, bool seeded) { - var expectedValues = new int[][] - { - new int[] {1559595546, 1755192844, 1649316166, 1198642031, 442452829, 1200195957, 1945678308, 949569752, 2099272109, 587775847}, - new int[] {534011718, 237820880, 1002897798, 1657007234, 1412011072, 929393559, 760389092, 2026928803, 217468053, 1379662799}, - new int[] {1655911537, 867932563, 356479430, 2115372437, 234085668, 658591161, 1722583523, 956804207, 483147644, 24066104}, - new int[] {630327709, 1498044246, 1857544709, 426253993, 1203643911, 387788763, 537294307, 2034163258, 748827235, 815953056}, - new int[] {1752227528, 2128155929, 1211126341, 884619196, 25718507, 116986365, 1499488738, 964038662, 1014506826, 1607840008}, - new int[] {726643700, 610783965, 564707973, 1342984399, 995276750, 1993667614, 314199522, 2041397713, 1280186417, 252243313}, - new int[] {1848543519, 1240895648, 2065773252, 1801349602, 1964834993, 1722865216, 1276393953, 971273117, 1545866008, 1044130265}, - new int[] {822959691, 1871007331, 1419354884, 112231158, 786909589, 1452062818, 91104737, 2048632168, 1811545599, 1836017217}, - new int[] {1944859510, 353635367, 772936516, 570596361, 1756467832, 1181260420, 1053299168, 978507572, 2077225190, 480420522}, - new int[] {919275682, 983747050, 126518148, 1028961564, 578542428, 910458022, 2015493599, 2055866623, 195421134, 1272307474}, - new int[] {2041175501, 1613858733, 1627583427, 1487326767, 1548100671, 639655624, 830204383, 985742027, 461100725, 2064194426}, - new int[] {1015591673, 96486769, 981165059, 1945691970, 370175267, 368853226, 1792398814, 2063101078, 726780316, 708597731}, - new int[] {2137491492, 726598452, 334746691, 256573526, 1339733510, 98050828, 607109598, 992976482, 992459907, 1500484683}, - new int[] {1111907664, 1356710135, 1835811970, 714938729, 161808106, 1974732077, 1569304029, 2070335533, 1258139498, 144887988}, - new int[] {86323836, 1986821818, 1189393602, 1173303932, 1131366349, 1703929679, 384014813, 1000210937, 1523819089, 936774940}, - new int[] {1208223655, 469449854, 542975234, 1631669135, 2100924592, 1433127281, 1346209244, 2077569988, 1789498680, 1728661892}, - new int[] {182639827, 1099561537, 2044040513, 2090034338, 922999188, 1162324883, 160920028, 1007445392, 2055178271, 373065197}, - new int[] {1304539646, 1729673220, 1397622145, 400915894, 1892557431, 891522485, 1123114459, 2084804443, 173374215, 1164952149}, - new int[] {278955818, 212301256, 751203777, 859281097, 714632027, 620720087, 2085308890, 1014679847, 439053806, 1956839101}, - new int[] {1400855637, 842412939, 104785409, 1317646300, 1684190270, 349917689, 900019674, 2092038898, 704733397, 601242406}, - }; - return (expectedValues); + Random r = Create(derived, seeded); + + var hs = new HashSet(); + for (int i = 0; i < 10_000; i++) + { + hs.Add(r.NextInt64(4)); + } + + for (long i = 0; i < 4; i++) + { + Assert.Contains(i, hs); + } + + Assert.DoesNotContain(-1L, hs); + Assert.DoesNotContain(4L, hs); } - [Fact] - public static void ExpectedValues() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Next_LongLong_AllValuesWithinSmallRangeHit(bool derived, bool seeded) + { + Random r = Create(derived, seeded); + + var hs = new HashSet(); + for (int i = 0; i < 10_000; i++) + { + hs.Add(r.NextInt64(42, 44)); + } + + for (long i = 42; i < 44; i++) + { + Assert.Contains(i, hs); + } + + Assert.DoesNotContain(41L, hs); + Assert.DoesNotContain(44L, hs); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CtorWithSeed_SequenceIsRepeatable(bool derived) + { + Random r1 = Create(derived, seeded: true); + Random r2 = Create(derived, seeded: true); + + for (int i = 0; i < 2; i++) + { + byte[] b1 = new byte[1000]; + byte[] b2 = new byte[1000]; + if (i == 0) + { + r1.NextBytes(b1); + r2.NextBytes(b2); + } + else + { + r1.NextBytes((Span)b1); + r2.NextBytes((Span)b2); + } + AssertExtensions.SequenceEqual(b1, b2); + } + + for (int i = 0; i < 1000; i++) + { + Assert.Equal(r1.Next(), r2.Next()); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ExpectedValues(bool derived) { - int[][] expectedValues = Values(); + // Random has a predictable sequence of values it generates based on its seed. + // So that we'll be made aware if a change to the implementation causes these + // sequences to change, this test verifies the first few numbers for a few seeds. + int[][] expectedValues = new int[][] + { + new int[] { 1559595546, 1755192844, 1649316166, 1198642031, 442452829, 1200195957, 1945678308, 949569752, 2099272109, 587775847 }, + new int[] { 534011718, 237820880, 1002897798, 1657007234, 1412011072, 929393559, 760389092, 2026928803, 217468053, 1379662799 }, + new int[] { 1655911537, 867932563, 356479430, 2115372437, 234085668, 658591161, 1722583523, 956804207, 483147644, 24066104 }, + new int[] { 630327709, 1498044246, 1857544709, 426253993, 1203643911, 387788763, 537294307, 2034163258, 748827235, 815953056 }, + new int[] { 1752227528, 2128155929, 1211126341, 884619196, 25718507, 116986365, 1499488738, 964038662, 1014506826, 1607840008 }, + new int[] { 726643700, 610783965, 564707973, 1342984399, 995276750, 1993667614, 314199522, 2041397713, 1280186417, 252243313 }, + new int[] { 1848543519, 1240895648, 2065773252, 1801349602, 1964834993, 1722865216, 1276393953, 971273117, 1545866008, 1044130265 }, + new int[] { 822959691, 1871007331, 1419354884, 112231158, 786909589, 1452062818, 91104737, 2048632168, 1811545599, 1836017217 }, + new int[] { 1944859510, 353635367, 772936516, 570596361, 1756467832, 1181260420, 1053299168, 978507572, 2077225190, 480420522 }, + new int[] { 919275682, 983747050, 126518148, 1028961564, 578542428, 910458022, 2015493599, 2055866623, 195421134, 1272307474 }, + new int[] { 2041175501, 1613858733, 1627583427, 1487326767, 1548100671, 639655624, 830204383, 985742027, 461100725, 2064194426 }, + new int[] { 1015591673, 96486769, 981165059, 1945691970, 370175267, 368853226, 1792398814, 2063101078, 726780316, 708597731 }, + new int[] { 2137491492, 726598452, 334746691, 256573526, 1339733510, 98050828, 607109598, 992976482, 992459907, 1500484683 }, + new int[] { 1111907664, 1356710135, 1835811970, 714938729, 161808106, 1974732077, 1569304029, 2070335533, 1258139498, 144887988 }, + new int[] { 86323836, 1986821818, 1189393602, 1173303932, 1131366349, 1703929679, 384014813, 1000210937, 1523819089, 936774940 }, + new int[] { 1208223655, 469449854, 542975234, 1631669135, 2100924592, 1433127281, 1346209244, 2077569988, 1789498680, 1728661892 }, + new int[] { 182639827, 1099561537, 2044040513, 2090034338, 922999188, 1162324883, 160920028, 1007445392, 2055178271, 373065197 }, + new int[] { 1304539646, 1729673220, 1397622145, 400915894, 1892557431, 891522485, 1123114459, 2084804443, 173374215, 1164952149 }, + new int[] { 278955818, 212301256, 751203777, 859281097, 714632027, 620720087, 2085308890, 1014679847, 439053806, 1956839101 }, + new int[] { 1400855637, 842412939, 104785409, 1317646300, 1684190270, 349917689, 900019674, 2092038898, 704733397, 601242406 }, + }; + for (int seed = 0; seed < expectedValues.Length; seed++) { - var r = new Random(seed); + Random r = derived ? new SubRandom(seed) : new Random(seed); for (int i = 0; i < expectedValues[seed].Length; i++) { Assert.Equal(expectedValues[seed][i], r.Next()); @@ -97,9 +249,12 @@ public static void ExpectedValues() } } - private static byte[][] ByteValues() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void ExpectedValues_NextBytes(bool derived) { - var expectedValues = new byte[][] + byte[][] expectedValues = new byte[][] { new byte[] { 0x1A, 0xC, 0x46, 0x6F, 0x5D, 0x75, 0xE4, 0xD8, 0xAD, 0x67 }, new byte[] { 0x46, 0xD0, 0x86, 0x82, 0x40, 0x97, 0xE4, 0xA3, 0x95, 0xCF }, @@ -122,27 +277,32 @@ private static byte[][] ByteValues() new byte[] { 0x2A, 0xC8, 0xC1, 0xC9, 0x5B, 0xD7, 0xDA, 0x27, 0xEE, 0xBD }, new byte[] { 0x55, 0x8B, 0x1, 0xDC, 0x3E, 0xF9, 0xDA, 0xF2, 0xD5, 0x26 } }; - return (expectedValues); - } - [Fact] - public static void ExpectedValues_NextBytesArray() - { - byte[][] expectedValues = ByteValues(); for (int seed = 0; seed < expectedValues.Length; seed++) { byte[] actualValues = new byte[expectedValues[seed].Length]; - var r = new Random(seed); + Random r = derived ? new SubRandom(seed) : new Random(seed); + r.NextBytes(actualValues); - Assert.Equal(expectedValues[seed], actualValues); + AssertExtensions.SequenceEqual(expectedValues[seed], actualValues); + } + + for (int seed = 0; seed < expectedValues.Length; seed++) + { + byte[] actualValues = new byte[expectedValues[seed].Length]; + Random r = derived ? new SubRandom(seed) : new Random(seed); + + r.NextBytes((Span)actualValues); + AssertExtensions.SequenceEqual(expectedValues[seed], actualValues); } } - [Fact] - public static void Sample() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Sample(bool seeded) { - SubRandom r = new SubRandom(); - + SubRandom r = seeded ? new SubRandom(42) : new SubRandom(); for (int i = 0; i < 1000; i++) { double d = r.ExposeSample(); @@ -150,56 +310,269 @@ public static void Sample() } } - private class SubRandom : Random + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SampleOrNext_DerivedOverrideCalledWhereExpected(bool seeded) { - public double ExposeSample() + SubRandom r; + + r = seeded ? new SubRandom(42) : new SubRandom(); + Assert.False(r.SampleCalled); + + foreach (int maxValue in new[] { 0, 1, 42 }) + { + r = seeded ? new SubRandom(42) : new SubRandom(); + r.Next(maxValue); + Assert.True(r.SampleCalled); + } + + foreach ((int minValue, int maxValue) in new[] { (0, 0), (0, 1), (42, 47) }) { - return Sample(); + r = seeded ? new SubRandom(42) : new SubRandom(); + r.Next(minValue, maxValue); + Assert.True(r.SampleCalled); } + + foreach (long maxValue in new[] { 42L, (long)int.MaxValue + 1 }) + { + r = seeded ? new SubRandom(42) : new SubRandom(); + r.NextInt64(maxValue); + Assert.True(r.NextCalled); + } + + foreach ((long minValue, long maxValue) in new[] { (42L, 47L), ((long)int.MaxValue + 1, long.MaxValue) }) + { + r = seeded ? new SubRandom(42) : new SubRandom(); + r.NextInt64(minValue, maxValue); + Assert.True(r.NextCalled); + } + + r = seeded ? new SubRandom(42) : new SubRandom(); + r.NextSingle(); + Assert.True(r.SampleCalled); + + r = seeded ? new SubRandom(42) : new SubRandom(); + r.NextDouble(); + Assert.True(r.SampleCalled); + + r = seeded ? new SubRandom(42) : new SubRandom(); + r.NextBytes((Span)new byte[1]); + Assert.True(r.NextCalled); + + // Next was changed to not call Sample in .NET Framework 2.0. + // NextBytes(byte[]) just uses Next. + // And NextInt64 uses NextBytes(byte[]). + // NextInt64(long{, long}) will use Next, but not if the range is such that the min will always be returned. + + r = seeded ? new SubRandom(42) : new SubRandom(); + r.Next(); + r.NextInt64(); + r.NextInt64(0); + r.NextInt64(1); + r.NextInt64(0, 0); + r.NextInt64(0, 1); + r.NextBytes((Span)new byte[1]); + Assert.False(r.SampleCalled); } - [Fact] - public static void Empty_Span() + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void Empty_Success(bool derived, bool seeded) { - int seed = Environment.TickCount; - Random r = new Random(seed); + Random r = Create(derived, seeded); + r.NextBytes(new byte[0]); r.NextBytes(Span.Empty); } [Fact] - public static void Seeded_Span() + public void Xoshiro_AlgorithmBehavesAsExpected() { - int seed = Environment.TickCount; + // This test is validating implementation detail. If the algorithm used by `new Random()` is ever + // updated, this test will need to be updated as well. - Random r1 = new Random(seed); - Random r2 = new Random(seed); + // One and only one of Xoshiro128StarStar and Xoshiro256StarStar should be in a given build. + Type implType = typeof(Random) + .GetNestedTypes(BindingFlags.NonPublic) + .Single(t => t.Name.StartsWith("Xoshiro", StringComparison.Ordinal)); + Assert.NotNull(implType); - Span s1 = new Span(new byte[1000]); - r1.NextBytes(s1); - Span s2 = new Span(new byte[1000]); - r2.NextBytes(s2); - for (int i = 0; i < s1.Length; i++) + var randOuter = new Random(); + object randInner = randOuter.GetType().GetField("_impl", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(randOuter); + Assert.NotNull(randInner); + + Type t = randInner.GetType(); + FieldInfo s0 = t.GetField("_s0", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo s1 = t.GetField("_s1", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo s2 = t.GetField("_s2", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo s3 = t.GetField("_s3", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(s0); + Assert.NotNull(s1); + Assert.NotNull(s2); + Assert.NotNull(s3); + + if (IntPtr.Size == 8) { - Assert.Equal(s1[i], s2[i]); + // Example seeds from https://www.pcg-random.org/posts/a-quick-look-at-xoshiro256.html + s0.SetValue(randInner, 0x01d353e5f3993bb0ul); + s1.SetValue(randInner, 0x7b9c0df6cb193b20ul); + s2.SetValue(randInner, 0xfdfcaa91110765b6ul); + s3.SetValue(randInner, 0xd2db341f10bb232eul); + + var buffer = new byte[256]; + randOuter.NextBytes(buffer); + + Assert.Contains("xoshiro256** by Blackman & Vigna", Encoding.ASCII.GetString(buffer)); + AssertExtensions.SequenceEqual(new byte[] + { + 0xdd, 0x51, 0xb2, 0xb7, 0xd9, 0x30, 0x3a, 0x37, 0xeb, 0xd9, 0x63, 0x66, 0xa6, 0x70, 0xfd, 0x50, + 0x26, 0xe7, 0x29, 0x1f, 0x21, 0x21, 0xc0, 0x35, 0x36, 0xc1, 0x2d, 0x03, 0x77, 0xb1, 0x41, 0xd3, + 0x43, 0x33, 0x2f, 0x77, 0xf7, 0xfe, 0x97, 0x01, 0x1e, 0x93, 0xc3, 0xce, 0xe4, 0xdf, 0xfc, 0xc4, + 0xdb, 0x6c, 0x06, 0x54, 0x08, 0x25, 0x6f, 0x5a, 0x0e, 0x86, 0x82, 0x4d, 0x1c, 0x72, 0xc9, 0x50, + 0x20, 0xae, 0xca, 0x84, 0xd9, 0x24, 0x87, 0xb9, 0x51, 0x96, 0x93, 0xae, 0xae, 0xd2, 0x8f, 0xce, + 0x57, 0x37, 0xc1, 0x5c, 0xf4, 0xcc, 0x5c, 0xd6, 0x2a, 0x29, 0x72, 0xcb, 0xf0, 0xc5, 0xf8, 0xf8, + 0x46, 0x1e, 0x33, 0xa2, 0x5d, 0xb1, 0x66, 0xb4, 0x15, 0x6f, 0x3b, 0xed, 0x93, 0xe4, 0x70, 0xba, + 0x11, 0xbe, 0x24, 0xb0, 0x20, 0x64, 0x13, 0x86, 0x71, 0x72, 0x92, 0x31, 0xd8, 0xbe, 0x03, 0xa9, + 0x78, 0x6f, 0x73, 0x68, 0x69, 0x72, 0x6f, 0x32, 0x35, 0x36, 0x2a, 0x2a, 0x20, 0x62, 0x79, 0x20, + 0x42, 0x6c, 0x61, 0x63, 0x6b, 0x6d, 0x61, 0x6e, 0x20, 0x26, 0x20, 0x56, 0x69, 0x67, 0x6e, 0x61, + 0xbd, 0x9a, 0xf9, 0xbd, 0x3a, 0x79, 0x52, 0xd3, 0x76, 0x50, 0x5e, 0x1e, 0x55, 0x6a, 0x36, 0x48, + 0x9f, 0xc0, 0x39, 0xc2, 0x5c, 0xdb, 0x99, 0xa3, 0x5c, 0xd5, 0x4b, 0xa2, 0x15, 0x35, 0x53, 0x9c, + 0xda, 0xdd, 0xc6, 0x0b, 0xbf, 0x33, 0xef, 0xa7, 0x82, 0xeb, 0x06, 0x52, 0x6d, 0x6d, 0x31, 0x2b, + 0x24, 0x7a, 0x0c, 0x3f, 0x70, 0x43, 0xd1, 0x6f, 0xaa, 0xc6, 0x88, 0x7e, 0xf9, 0x30, 0xee, 0xff, + 0x22, 0x31, 0xaf, 0xc6, 0x1f, 0xe5, 0x68, 0x22, 0xe9, 0x6e, 0x30, 0x06, 0xf6, 0x7f, 0x9a, 0x6e, + 0xbe, 0x19, 0x0c, 0xf7, 0xae, 0xe2, 0xfa, 0xec, 0x8e, 0xc6, 0x22, 0xe1, 0x78, 0xb6, 0x39, 0xd1, + }, buffer); + + Assert.Equal(50101881, randOuter.Next()); + Assert.Equal(1272175254, randOuter.Next()); + Assert.Equal(0, randOuter.Next(0)); + Assert.Equal(0, randOuter.Next(1)); + + Assert.Equal(11, randOuter.Next(42)); + Assert.Equal(1865324524, randOuter.Next(int.MaxValue)); + + Assert.Equal(0, randOuter.Next(0, 0)); + Assert.Equal(1, randOuter.Next(1, 2)); + Assert.Equal(12, randOuter.Next(0, 42)); + Assert.Equal(7234, randOuter.Next(42, 12345)); + Assert.Equal(2147483642, randOuter.Next(int.MaxValue - 5, int.MaxValue)); + Assert.Equal(1981894504, randOuter.Next(int.MinValue, int.MaxValue)); + + Assert.Equal(3644728249650840822, randOuter.NextInt64()); + Assert.Equal(2809750975933744783, randOuter.NextInt64()); + + Assert.Equal(0, randOuter.NextInt64(0)); + Assert.Equal(0, randOuter.NextInt64(1)); + Assert.Equal(35, randOuter.NextInt64(42)); + Assert.Equal(7986543274318426717, randOuter.NextInt64(long.MaxValue)); + + Assert.Equal(0, randOuter.NextInt64(0, 0)); + Assert.Equal(1, randOuter.NextInt64(1, 2)); + Assert.Equal(15, randOuter.NextInt64(0, 42)); + Assert.Equal(4155, randOuter.NextInt64(42, 12345)); + Assert.Equal(9223372036854775803, randOuter.NextInt64(long.MaxValue - 5, long.MaxValue)); + Assert.Equal(375288451405801266, randOuter.NextInt64(long.MinValue, long.MaxValue)); + + Assert.Equal(0.2885307561293763, randOuter.NextDouble()); + Assert.Equal(0.8319616593420064, randOuter.NextDouble()); + Assert.Equal(0.694751074593599, randOuter.NextDouble()); + + Assert.Equal(0.7749006f, randOuter.NextSingle()); + Assert.Equal(0.13424736f, randOuter.NextSingle()); + Assert.Equal(0.05282557f, randOuter.NextSingle()); } - for (int i = 0; i < s1.Length; i++) + else { - int x1 = r1.Next(); - int x2 = r2.Next(); - Assert.Equal(x1, x2); + s0.SetValue(randInner, 0x01d353e5u); + s1.SetValue(randInner, 0x7b9c0df6u); + s2.SetValue(randInner, 0xfdfcaa91u); + s3.SetValue(randInner, 0xd2db341fu); + + var buffer = new byte[128]; + randOuter.NextBytes(buffer); + AssertExtensions.SequenceEqual(new byte[] + { + 0xDD, 0x20, 0x3A, 0x37, 0xEB, 0x6F, 0xFD, 0x50, 0xA3, 0x7B, 0xCD, 0x37, 0xA8, 0xAA, 0x19, 0xA8, + 0x22, 0xD6, 0x21, 0x57, 0x55, 0xF3, 0xA2, 0x56, 0x73, 0x30, 0x61, 0xDE, 0x62, 0xD8, 0x02, 0xB9, + 0x5C, 0xAE, 0x3E, 0x2D, 0xC8, 0xD6, 0xBF, 0x7D, 0x6D, 0x86, 0xCE, 0x95, 0x3F, 0x7C, 0xF0, 0x86, + 0x36, 0x26, 0xB8, 0xA7, 0x5C, 0x80, 0xC8, 0xA7, 0xAC, 0x2C, 0xE6, 0x0E, 0x25, 0x6F, 0xEB, 0x04, + 0x22, 0xDE, 0xB4, 0xB6, 0x48, 0xB2, 0x07, 0x79, 0x09, 0xA8, 0xF6, 0x42, 0xA8, 0x5C, 0x3F, 0xCE, + 0x11, 0xE9, 0x91, 0x8B, 0x17, 0x48, 0x0B, 0xE1, 0xEB, 0x0A, 0x89, 0xC1, 0x64, 0x3B, 0x58, 0x76, + 0x30, 0x53, 0x67, 0x13, 0x68, 0xAC, 0xF3, 0x5D, 0x1B, 0x84, 0xF5, 0x88, 0x42, 0xC7, 0x45, 0x74, + 0x65, 0xB5, 0x11, 0xF2, 0x0D, 0x3F, 0x62, 0xC8, 0x5C, 0x7C, 0x1C, 0x35, 0x34, 0x2D, 0xBC, 0x9E, + }, buffer); + + Assert.Equal(1539844677, randOuter.Next()); + Assert.Equal(1451010027, randOuter.Next()); + Assert.Equal(0, randOuter.Next(0)); + Assert.Equal(0, randOuter.Next(1)); + + Assert.Equal(23, randOuter.Next(42)); + Assert.Equal(1207874445, randOuter.Next(int.MaxValue)); + + Assert.Equal(0, randOuter.Next(0, 0)); + Assert.Equal(1, randOuter.Next(1, 2)); + Assert.Equal(33, randOuter.Next(0, 42)); + Assert.Equal(2525, randOuter.Next(42, 12345)); + Assert.Equal(2147483646, randOuter.Next(int.MaxValue - 5, int.MaxValue)); + Assert.Equal(-1841045958, randOuter.Next(int.MinValue, int.MaxValue)); + + Assert.Equal(364988307769675967, randOuter.NextInt64()); + Assert.Equal(4081751239945971648, randOuter.NextInt64()); + + Assert.Equal(0, randOuter.NextInt64(0)); + Assert.Equal(0, randOuter.NextInt64(1)); + Assert.Equal(8, randOuter.NextInt64(42)); + Assert.Equal(3127675200855610302, randOuter.NextInt64(long.MaxValue)); + + Assert.Equal(0, randOuter.NextInt64(0, 0)); + Assert.Equal(1, randOuter.NextInt64(1, 2)); + Assert.Equal(25, randOuter.NextInt64(0, 42)); + Assert.Equal(593, randOuter.NextInt64(42, 12345)); + Assert.Equal(9223372036854775805, randOuter.NextInt64(long.MaxValue - 5, long.MaxValue)); + Assert.Equal(-1415073976784572606, randOuter.NextInt64(long.MinValue, long.MaxValue)); + + Assert.Equal(0.054582986776168796, randOuter.NextDouble()); + Assert.Equal(0.7599686772523376, randOuter.NextDouble()); + Assert.Equal(0.9113759792165226, randOuter.NextDouble()); + + Assert.Equal(0.3010761f, randOuter.NextSingle()); + Assert.Equal(0.8162224f, randOuter.NextSingle()); + Assert.Equal(0.5866389f, randOuter.NextSingle()); } } - [Fact] - public static void ExpectedValues_NextBytesSpan() + private static Random Create(bool derived, bool seeded, int seed = 42) => + (derived, seeded) switch + { + (false, false) => new Random(), + (false, true) => new Random(42), + (true, false) => new SubRandom(), + (true, true) => new SubRandom(42) + }; + + private class SubRandom : Random { - byte[][] expectedValues = ByteValues(); - for (int seed = 0; seed < expectedValues.Length; seed++) + public bool SampleCalled, NextCalled; + + public SubRandom() { } + public SubRandom(int Seed) : base(Seed) { } + + public double ExposeSample() => Sample(); + + protected override double Sample() { - byte[] actualValues = new byte[expectedValues[seed].Length]; - var r = new Random(seed); - r.NextBytes(new Span(actualValues)); - Assert.Equal(expectedValues[seed], actualValues); + SampleCalled = true; + return base.Sample(); + } + + public override int Next() + { + NextCalled = true; + return base.Next(); } } } diff --git a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs index fd453d7535118..41d11ebdc96a9 100644 --- a/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs +++ b/src/libraries/System.Runtime.InteropServices.RuntimeInformation/tests/DescriptionNameTests.cs @@ -73,7 +73,6 @@ public void DumpRuntimeInformationToConsole() sb.AppendFormat($"###\tArchitecture: {RuntimeInformation.ProcessArchitecture.ToString()}").AppendLine(); foreach (string prop in new string[] { - #pragma warning disable 0618 // some of these Int32-returning properties are marked obsolete nameof(p.BasePriority), nameof(p.HandleCount), nameof(p.Id), @@ -83,21 +82,14 @@ public void DumpRuntimeInformationToConsole() nameof(p.MainWindowTitle), nameof(p.MaxWorkingSet), nameof(p.MinWorkingSet), - nameof(p.NonpagedSystemMemorySize), nameof(p.NonpagedSystemMemorySize64), - nameof(p.PagedMemorySize), nameof(p.PagedMemorySize64), - nameof(p.PagedSystemMemorySize), nameof(p.PagedSystemMemorySize64), - nameof(p.PeakPagedMemorySize), nameof(p.PeakPagedMemorySize64), - nameof(p.PeakVirtualMemorySize), nameof(p.PeakVirtualMemorySize64), - nameof(p.PeakWorkingSet), nameof(p.PeakWorkingSet64), nameof(p.PriorityBoostEnabled), nameof(p.PriorityClass), - nameof(p.PrivateMemorySize), nameof(p.PrivateMemorySize64), nameof(p.PrivilegedProcessorTime), nameof(p.ProcessName), @@ -107,11 +99,8 @@ public void DumpRuntimeInformationToConsole() nameof(p.StartTime), nameof(p.TotalProcessorTime), nameof(p.UserProcessorTime), - nameof(p.VirtualMemorySize), nameof(p.VirtualMemorySize64), - nameof(p.WorkingSet), nameof(p.WorkingSet64), - #pragma warning restore 0618 }) { sb.Append($"###\t{prop}: "); @@ -131,7 +120,18 @@ public void DumpRuntimeInformationToConsole() if (osd.Contains("Linux")) { // Dump several procfs files and /etc/os-release - foreach (string path in new string[] { "/proc/self/mountinfo", "/proc/self/cgroup", "/proc/self/limits", "/etc/os-release", "/etc/sysctl.conf", "/proc/meminfo" }) + foreach (string path in new string[] { + "/proc/self/mountinfo", + "/proc/self/cgroup", + "/proc/self/limits", + "/etc/os-release", + "/etc/sysctl.conf", + "/proc/meminfo", + "/proc/sys/vm/oom_kill_allocating_task", + "/proc/sys/kernel/core_pattern", + "/proc/sys/kernel/core_uses_pid", + "/proc/sys/kernel/coredump_filter" + }) { Console.WriteLine($"### CONTENTS OF \"{path}\":"); try 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 da17c552dd818..e8132bb910a2b 100644 --- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs +++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs @@ -601,6 +601,7 @@ public static void GetNativeVariantForObject(T? obj, System.IntPtr pDstNative public static string GetTypeInfoName(System.Runtime.InteropServices.ComTypes.ITypeInfo typeInfo) { throw null; } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] public static object GetUniqueObjectForIUnknown(System.IntPtr unknown) { throw null; } + public static void InitHandle(SafeHandle safeHandle, IntPtr handle) { } public static bool IsComObject(object o) { throw null; } public static bool IsTypeVisibleFromCom(System.Type t) { throw null; } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] @@ -1056,6 +1057,8 @@ public enum CreateObjectFlags None = 0, TrackerObject = 1, UniqueInstance = 2, + Aggregation = 4, + Unwrap = 8, } [System.Runtime.Versioning.SupportedOSPlatformAttribute("windows")] [System.CLSCompliantAttribute(false)] @@ -1076,6 +1079,7 @@ public struct ComInterfaceDispatch public object GetOrCreateObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags) { throw null; } protected abstract object? CreateObject(System.IntPtr externalComObject, CreateObjectFlags flags); public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags, object wrapper) { throw null; } + public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags, object wrapper, System.IntPtr inner) { throw null; } protected abstract void ReleaseObjects(System.Collections.IEnumerable objects); public static void RegisterForTrackerSupport(ComWrappers instance) { } public static void RegisterForMarshalling(ComWrappers instance) { } diff --git a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj index 29c38f3766bfc..e7d065da06cea 100644 --- a/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj +++ b/src/libraries/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj @@ -93,6 +93,7 @@ + @@ -176,4 +177,4 @@ - \ No newline at end of file + diff --git a/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/InitHandleTests.cs b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/InitHandleTests.cs new file mode 100644 index 0000000000000..2ad614364040c --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/InitHandleTests.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 Xunit; + +namespace System.Runtime.InteropServices.Tests +{ + public class InitHandleTests + { + [Fact] + public void InitHandle_SetsHandle() + { + var safeHandle = new TestSafeHandle(); + nint underlyingHandle = 42; + + Marshal.InitHandle(safeHandle, underlyingHandle); + + Assert.Equal((IntPtr)underlyingHandle, safeHandle.DangerousGetHandle()); + } + + class TestSafeHandle : SafeHandle + { + public TestSafeHandle() : base(IntPtr.Zero, true) { } + + public override bool IsInvalid => handle == IntPtr.Zero; + + protected override bool ReleaseHandle() => true; + } + } +} diff --git a/src/libraries/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs b/src/libraries/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs index dc9d5f71b9f3b..ecfda2d0efd1f 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/ref/System.Runtime.Serialization.Formatters.cs @@ -16,10 +16,12 @@ protected Formatter() { } public abstract System.Runtime.Serialization.StreamingContext Context { get; set; } public abstract System.Runtime.Serialization.ISurrogateSelector? SurrogateSelector { get; set; } [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] public abstract object Deserialize(System.IO.Stream serializationStream); protected virtual object? GetNext(out long objID) { throw null; } protected virtual long Schedule(object? obj) { throw null; } [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] public abstract void Serialize(System.IO.Stream serializationStream, object graph); protected abstract void WriteArray(object obj, string name, System.Type memberType); protected abstract void WriteBoolean(bool val, string name); @@ -74,12 +76,13 @@ public static partial class FormatterServices { public static void CheckTypeSecurity(System.Type t, System.Runtime.Serialization.Formatters.TypeFilterLevel securityLevel) { } public static object?[] GetObjectData(object obj, System.Reflection.MemberInfo[] members) { throw null; } - public static object GetSafeUninitializedObject(System.Type type) { throw null; } - public static System.Reflection.MemberInfo[] GetSerializableMembers(System.Type type) { throw null; } - public static System.Reflection.MemberInfo[] GetSerializableMembers(System.Type type, System.Runtime.Serialization.StreamingContext context) { throw null; } + public static object GetSafeUninitializedObject([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type type) { throw null; } + public static System.Reflection.MemberInfo[] GetSerializableMembers([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type) { throw null; } + public static System.Reflection.MemberInfo[] GetSerializableMembers([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type, System.Runtime.Serialization.StreamingContext context) { throw null; } public static System.Runtime.Serialization.ISerializationSurrogate GetSurrogateForCyclicalReference(System.Runtime.Serialization.ISerializationSurrogate innerSurrogate) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Types might be removed")] public static System.Type? GetTypeFromAssembly(System.Reflection.Assembly assem, string name) { throw null; } - public static object GetUninitializedObject(System.Type type) { throw null; } + public static object GetUninitializedObject([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicConstructors | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] System.Type type) { throw null; } public static object PopulateObjectMembers(object obj, System.Reflection.MemberInfo[] members, object?[] data) { throw null; } } public partial interface IFormatter @@ -88,8 +91,10 @@ public partial interface IFormatter System.Runtime.Serialization.StreamingContext Context { get; set; } System.Runtime.Serialization.ISurrogateSelector? SurrogateSelector { get; set; } [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] object Deserialize(System.IO.Stream serializationStream); [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] void Serialize(System.IO.Stream serializationStream, object graph); } public partial interface ISerializationSurrogate @@ -112,17 +117,23 @@ public ObjectIDGenerator() { } public partial class ObjectManager { public ObjectManager(System.Runtime.Serialization.ISurrogateSelector? selector, System.Runtime.Serialization.StreamingContext context) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public virtual void DoFixups() { } public virtual object? GetObject(long objectID) { throw null; } public virtual void RaiseDeserializationEvent() { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public void RaiseOnDeserializingEvent(object obj) { } public virtual void RecordArrayElementFixup(long arrayToBeFixed, int index, long objectRequired) { } public virtual void RecordArrayElementFixup(long arrayToBeFixed, int[] indices, long objectRequired) { } public virtual void RecordDelayedFixup(long objectToBeFixed, string memberName, long objectRequired) { } public virtual void RecordFixup(long objectToBeFixed, System.Reflection.MemberInfo member, long objectRequired) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public virtual void RegisterObject(object obj, long objectID) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo info) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo? info, long idOfContainingObj, System.Reflection.MemberInfo? member) { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public void RegisterObject(object obj, long objectID, System.Runtime.Serialization.SerializationInfo? info, long idOfContainingObj, System.Reflection.MemberInfo? member, int[]? arrayIndex) { } } public abstract partial class SerializationBinder @@ -135,6 +146,7 @@ public sealed partial class SerializationObjectManager { public SerializationObjectManager(System.Runtime.Serialization.StreamingContext context) { } public void RaiseOnSerializedEvent() { } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("SerializationObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered.")] public void RegisterObject(object obj) { } } public partial class SurrogateSelector : System.Runtime.Serialization.ISurrogateSelector @@ -184,8 +196,10 @@ public BinaryFormatter(System.Runtime.Serialization.ISurrogateSelector? selector public System.Runtime.Serialization.ISurrogateSelector? SurrogateSelector { get { throw null; } set { } } public System.Runtime.Serialization.Formatters.FormatterTypeStyle TypeFormat { get { throw null; } set { } } [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] public object Deserialize(System.IO.Stream serializationStream) { throw null; } [System.ObsoleteAttribute("BinaryFormatter serialization is obsolete and should not be used. See https://aka.ms/binaryformatter for more information.", DiagnosticId = "SYSLIB0011", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered.")] public void Serialize(System.IO.Stream serializationStream, object graph) { } } } diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Suppressions.xml deleted file mode 100644 index 9b389862e0c92..0000000000000 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/ILLink/ILLink.Suppressions.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - ILLink - IL2026 - member - M:System.Runtime.Serialization.Formatters.Binary.ObjectReader.TopLevelAssemblyTypeResolver.ResolveType(System.Reflection.Assembly,System.String,System.Boolean) - - - ILLink - IL2026 - member - M:System.Runtime.Serialization.FormatterServices.GetTypeFromAssembly(System.Reflection.Assembly,System.String) - - - ILLink - IL2057 - member - M:System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetSimplyNamedTypeFromAssembly(System.Reflection.Assembly,System.String,System.Type@) - - - ILLink - IL2067 - member - M:System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(System.Type) - - - ILLink - IL2070 - member - M:System.Runtime.Serialization.FormatterServices.GetSerializableFields(System.Type) - - - ILLink - IL2070 - member - M:System.Runtime.Serialization.ObjectManager.GetDeserializationConstructor(System.Type) - - - ILLink - IL2070 - member - M:System.Runtime.Serialization.SerializationEvents.GetMethodsWithAttribute(System.Type,System.Type) - - - ILLink - IL2075 - member - M:System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(System.Type) - - - ILLink - IL2075 - member - M:System.Runtime.Serialization.ObjectManager.DoValueTypeFixup(System.Reflection.FieldInfo,System.Runtime.Serialization.ObjectHolder,System.Object) - - - ILLink - IL2077 - member - M:System.Runtime.Serialization.Formatters.Binary.ObjectReader.ParseObject(System.Runtime.Serialization.Formatters.Binary.ParseRecord) - - - \ No newline at end of file diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs index 5d97328d8569d..8748e6901547d 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatter.cs @@ -1,10 +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.IO; -using System.Globalization; using System.Collections; -using System.Reflection; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; namespace System.Runtime.Serialization { @@ -21,6 +21,7 @@ protected Formatter() } [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public abstract object Deserialize(Stream serializationStream); protected virtual object? GetNext(out long objID) @@ -61,6 +62,7 @@ protected virtual long Schedule(object? obj) } [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public abstract void Serialize(Stream serializationStream, object graph); protected abstract void WriteArray(object obj, string name, Type memberType); diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs index 3403898e2e2b3..b3f94a260d7c5 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/FormatterServices.cs @@ -1,10 +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.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization.Formatters; @@ -16,7 +16,11 @@ public static class FormatterServices { private static readonly ConcurrentDictionary s_memberInfoTable = new ConcurrentDictionary(); - private static FieldInfo[] InternalGetSerializableMembers(Type type) + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", + Justification = "The Type is annotated with All, which will preserve base type fields.")] + private static FieldInfo[] InternalGetSerializableMembers( + // currently the only way to preserve base, non-public fields is to use All + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type) { Debug.Assert(type != null); @@ -79,7 +83,8 @@ private static FieldInfo[] InternalGetSerializableMembers(Type type) return typeMembers; } - private static FieldInfo[] GetSerializableFields(Type type) + private static FieldInfo[] GetSerializableFields( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields)] Type type) { // Get the list of all fields FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); @@ -156,12 +161,15 @@ private static bool GetParentTypes(Type parentType, out Type[]? parentTypes, out return unique; } - public static MemberInfo[] GetSerializableMembers(Type type) + public static MemberInfo[] GetSerializableMembers( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type) { return GetSerializableMembers(type, new StreamingContext(StreamingContextStates.All)); } - public static MemberInfo[] GetSerializableMembers(Type type, StreamingContext context) + public static MemberInfo[] GetSerializableMembers( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, + StreamingContext context) { if (type == null) { @@ -189,7 +197,9 @@ public static object GetUninitializedObject( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) => RuntimeHelpers.GetUninitializedObject(type); - public static object GetSafeUninitializedObject(Type type) => RuntimeHelpers.GetUninitializedObject(type); + public static object GetSafeUninitializedObject( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) => RuntimeHelpers.GetUninitializedObject(type); internal static void SerializationSetValue(MemberInfo fi, object? target, object? value) { @@ -294,6 +304,7 @@ public static ISerializationSurrogate GetSurrogateForCyclicalReference(ISerializ return new SurrogateForCyclicalReference(innerSurrogate); } + [RequiresUnreferencedCode("Types might be removed")] public static Type? GetTypeFromAssembly(Assembly assem, string name) { if (assem == null) diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs index 939b78ad9535d..8d3e437a0b5c5 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.Core.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.CodeAnalysis; using System.IO; namespace System.Runtime.Serialization.Formatters.Binary @@ -8,6 +9,7 @@ namespace System.Runtime.Serialization.Formatters.Binary public sealed partial class BinaryFormatter : IFormatter { [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public object Deserialize(Stream serializationStream) { // don't refactor the 'throw' into a helper method; linker will have difficulty trimming @@ -58,6 +60,7 @@ public object Deserialize(Stream serializationStream) } [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public void Serialize(Stream serializationStream, object graph) { // don't refactor the 'throw' into a helper method; linker will have difficulty trimming diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs index 4d56deea06faf..59970dabe4a6b 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryFormatter.PlatformNotSupported.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.CodeAnalysis; using System.IO; namespace System.Runtime.Serialization.Formatters.Binary @@ -8,10 +9,12 @@ namespace System.Runtime.Serialization.Formatters.Binary public sealed partial class BinaryFormatter : IFormatter { [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public object Deserialize(Stream serializationStream) => throw new PlatformNotSupportedException(SR.BinaryFormatter_SerializationDisallowed); [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(IFormatter.RequiresUnreferencedCodeMessage)] public void Serialize(Stream serializationStream, object graph) => throw new PlatformNotSupportedException(SR.BinaryFormatter_SerializationDisallowed); } diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs index 64f4e6ddcd830..7d9036a907ebf 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectInfo.cs @@ -1,10 +1,11 @@ // 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; using System.Collections.Generic; -using System.Reflection; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Threading; namespace System.Runtime.Serialization.Formatters.Binary { @@ -15,6 +16,7 @@ internal sealed class WriteObjectInfo { internal int _objectInfoId; internal object? _obj; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] internal Type? _objectType; internal bool _isSi; @@ -65,6 +67,7 @@ private void InternalInit() _binderAssemblyString = null; } + [RequiresUnreferencedCode("It isn't possible to statically get the Type of object")] internal static WriteObjectInfo Serialize(object obj, ISurrogateSelector? surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder? binder) { WriteObjectInfo woi = GetObjectInfo(serObjectInfoInit); @@ -73,6 +76,7 @@ internal static WriteObjectInfo Serialize(object obj, ISurrogateSelector? surrog } // Write constructor + [RequiresUnreferencedCode("It isn't possible to statically get the Type of object")] internal void InitSerialize(object obj, ISurrogateSelector? surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, ObjectWriter objectWriter, SerializationBinder? binder) { _context = context; @@ -118,7 +122,13 @@ internal void InitSerialize(object obj, ISurrogateSelector? surrogateSelector, S } } - internal static WriteObjectInfo Serialize(Type objectType, ISurrogateSelector? surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SerializationBinder? binder) + internal static WriteObjectInfo Serialize( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + SerObjectInfoInit serObjectInfoInit, + IFormatterConverter converter, + SerializationBinder? binder) { WriteObjectInfo woi = GetObjectInfo(serObjectInfoInit); woi.InitSerialize(objectType, surrogateSelector, context, serObjectInfoInit, converter, binder); @@ -126,7 +136,13 @@ internal static WriteObjectInfo Serialize(Type objectType, ISurrogateSelector? s } // Write Constructor used for array types or null members - internal void InitSerialize(Type objectType, ISurrogateSelector? surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SerializationBinder? binder) + internal void InitSerialize( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + SerObjectInfoInit serObjectInfoInit, + IFormatterConverter converter, + SerializationBinder? binder) { _objectType = objectType; _context = context; @@ -313,6 +329,7 @@ internal sealed class ReadObjectInfo internal int _objectInfoId; internal static int _readObjectInfoCounter; + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] internal Type? _objectType; internal ObjectManager? _objectManager; @@ -347,14 +364,28 @@ internal void PrepareForReuse() _lastPosition = 0; } - internal static ReadObjectInfo Create(Type objectType, ISurrogateSelector? surrogateSelector, StreamingContext context, ObjectManager? objectManager, SerObjectInfoInit? serObjectInfoInit, IFormatterConverter? converter, bool bSimpleAssembly) + internal static ReadObjectInfo Create( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + ObjectManager? objectManager, + SerObjectInfoInit? serObjectInfoInit, + IFormatterConverter? converter, + bool bSimpleAssembly) { ReadObjectInfo roi = GetObjectInfo(serObjectInfoInit); roi.Init(objectType, surrogateSelector, context, objectManager, serObjectInfoInit, converter, bSimpleAssembly); return roi; } - internal void Init(Type objectType, ISurrogateSelector? surrogateSelector, StreamingContext context, ObjectManager? objectManager, SerObjectInfoInit? serObjectInfoInit, IFormatterConverter? converter, bool bSimpleAssembly) + internal void Init( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + ObjectManager? objectManager, + SerObjectInfoInit? serObjectInfoInit, + IFormatterConverter? converter, + bool bSimpleAssembly) { _objectType = objectType; _objectManager = objectManager; @@ -366,14 +397,32 @@ internal void Init(Type objectType, ISurrogateSelector? surrogateSelector, Strea InitReadConstructor(objectType, surrogateSelector, context); } - internal static ReadObjectInfo Create(Type? objectType, string[] memberNames, Type[]? memberTypes, ISurrogateSelector? surrogateSelector, StreamingContext context, ObjectManager? objectManager, SerObjectInfoInit? serObjectInfoInit, IFormatterConverter? converter, bool bSimpleAssembly) + internal static ReadObjectInfo Create( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? objectType, + string[] memberNames, + Type[]? memberTypes, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + ObjectManager? objectManager, + SerObjectInfoInit? serObjectInfoInit, + IFormatterConverter? converter, + bool bSimpleAssembly) { ReadObjectInfo roi = GetObjectInfo(serObjectInfoInit); roi.Init(objectType, memberNames, memberTypes, surrogateSelector, context, objectManager, serObjectInfoInit, converter, bSimpleAssembly); return roi; } - internal void Init(Type? objectType, string[] memberNames, Type[]? memberTypes, ISurrogateSelector? surrogateSelector, StreamingContext context, ObjectManager? objectManager, SerObjectInfoInit? serObjectInfoInit, IFormatterConverter? converter, bool bSimpleAssembly) + internal void Init( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? objectType, + string[] memberNames, + Type[]? memberTypes, + ISurrogateSelector? surrogateSelector, + StreamingContext context, + ObjectManager? objectManager, + SerObjectInfoInit? serObjectInfoInit, + IFormatterConverter? converter, + bool bSimpleAssembly) { _objectType = objectType; _objectManager = objectManager; diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs index cb1d8f45cd6b4..ae2f5423c27f2 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs @@ -1,16 +1,18 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Reflection; -using System.Diagnostics; -using System.Collections; namespace System.Runtime.Serialization.Formatters.Binary { internal sealed class ObjectReader { + private const string ObjectReaderUnreferencedCodeMessage = "ObjectReader requires unreferenced code"; + // System.Serializer information internal Stream _stream; internal ISurrogateSelector? _surrogates; @@ -74,6 +76,8 @@ internal ObjectReader(Stream stream, ISurrogateSelector? selector, StreamingCont _binder = binder; _formatterEnums = formatterEnums; } + + [RequiresUnreferencedCode("Types might be removed")] internal object Deserialize(BinaryParser serParser) { if (serParser == null) @@ -162,16 +166,21 @@ internal object CrossAppDomainArray(int index) return _crossAppDomainArray[index]; } - internal ReadObjectInfo CreateReadObjectInfo(Type objectType) + internal ReadObjectInfo CreateReadObjectInfo( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType) { return ReadObjectInfo.Create(objectType, _surrogates, _context, _objectManager, _serObjectInfoInit, _formatterConverter, _isSimpleAssembly); } - internal ReadObjectInfo CreateReadObjectInfo(Type? objectType, string[] memberNames, Type[]? memberTypes) + internal ReadObjectInfo CreateReadObjectInfo( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? objectType, + string[] memberNames, + Type[]? memberTypes) { return ReadObjectInfo.Create(objectType, memberNames, memberTypes, _surrogates, _context, _objectManager, _serObjectInfoInit, _formatterConverter, _isSimpleAssembly); } + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] internal void Parse(ParseRecord pr) { switch (pr._parseTypeEnum) @@ -218,6 +227,7 @@ private void ParseError(ParseRecord processing, ParseRecord onStack) private void ParseSerializedStreamHeaderEnd(ParseRecord pr) => _stack!.Pop(); // New object encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseObject(ParseRecord pr) { if (!_fullDeserialization) @@ -300,6 +310,7 @@ private void ParseObject(ParseRecord pr) } // End of object encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseObjectEnd(ParseRecord pr) { Debug.Assert(_stack != null); @@ -359,6 +370,7 @@ private void ParseObjectEnd(ParseRecord pr) } // Array object encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseArray(ParseRecord pr) { Debug.Assert(_stack != null); @@ -524,6 +536,7 @@ private void NextRectangleMap(ParseRecord pr) // Array object item encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseArrayMember(ParseRecord pr) { Debug.Assert(_stack != null); @@ -701,6 +714,7 @@ private void ParseArrayMember(ParseRecord pr) objectPr._memberIndex++; } + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseArrayMemberEnd(ParseRecord pr) { // If this is a nested array object, then pop the stack @@ -711,6 +725,7 @@ private void ParseArrayMemberEnd(ParseRecord pr) } // Object member encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseMember(ParseRecord pr) { Debug.Assert(_stack != null); @@ -825,6 +840,7 @@ private void ParseMember(ParseRecord pr) } // Object member end encountered in stream + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseMemberEnd(ParseRecord pr) { switch (pr._memberTypeEnum) @@ -846,6 +862,7 @@ private void ParseMemberEnd(ParseRecord pr) } // Processes a string object by getting an internal ID for it and registering it with the objectManager + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void ParseString(ParseRecord pr, ParseRecord parentPr) { // Process String class @@ -857,11 +874,13 @@ private void ParseString(ParseRecord pr, ParseRecord parentPr) } } + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void RegisterObject(object obj, ParseRecord pr, ParseRecord? objectPr) { RegisterObject(obj, pr, objectPr, false); } + [RequiresUnreferencedCode(ObjectReaderUnreferencedCodeMessage)] private void RegisterObject(object? obj, ParseRecord pr, ParseRecord? objectPr, bool bIsString) { if (!pr._isRegistered) @@ -937,6 +956,7 @@ internal long GetId(long objectId) return -1 * objectId; } + [RequiresUnreferencedCode("Types might be removed")] internal Type? Bind(string assemblyString, string typeString) { Type? type = null; @@ -957,6 +977,7 @@ internal sealed class TypeNAssembly public string? AssemblyName; } + [RequiresUnreferencedCode("Types might be removed")] internal Type? FastBindToType(string? assemblyName, string typeName) { Type? type = null; @@ -1046,6 +1067,7 @@ internal sealed class TypeNAssembly return null; } + [RequiresUnreferencedCode("Types might be removed")] private static void GetSimplyNamedTypeFromAssembly(Assembly assm, string typeName, ref Type? type) { // Catching any exceptions that could be thrown from a failure on assembly load @@ -1069,6 +1091,7 @@ private static void GetSimplyNamedTypeFromAssembly(Assembly assm, string typeNam private string? _previousName; private Type? _previousType; + [RequiresUnreferencedCode("Types might be removed")] internal Type? GetType(BinaryAssemblyInfo assemblyInfo, string name) { Type? objectType; @@ -1122,6 +1145,7 @@ public TopLevelAssemblyTypeResolver(Assembly topLevelAssembly) _topLevelAssembly = topLevelAssembly; } + [RequiresUnreferencedCode("Types might be removed")] public Type? ResolveType(Assembly? assembly, string simpleTypeName, bool ignoreCase) { if (assembly == null) diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs index 7466fd9132760..cf57a01929bb1 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectWriter.cs @@ -1,14 +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.Reflection; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Runtime.Serialization.Formatters.Binary { internal sealed class ObjectWriter { + private const string ObjectWriterUnreferencedCodeMessage = "ObjectWriter requires unreferenced code"; + private Queue? _objectQueue; private ObjectIDGenerator? _idGenerator; private int _currentId; @@ -47,6 +49,7 @@ internal ObjectWriter(ISurrogateSelector? selector, StreamingContext context, In _objectManager = new SerializationObjectManager(context); } + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] internal void Serialize(object graph, BinaryFormatterWriter serWriter) { if (graph == null) @@ -109,6 +112,7 @@ internal void Serialize(object graph, BinaryFormatterWriter serWriter) internal SerializationObjectManager ObjectManager => _objectManager; // Writes a given object to the stream. + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo) { object? obj = objectInfo._obj; @@ -198,6 +202,7 @@ private void Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo } // Writes a given object to the stream. + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void Write(WriteObjectInfo objectInfo, NameInfo? memberNameInfo, NameInfo typeNameInfo, @@ -248,6 +253,7 @@ private void Write(WriteObjectInfo objectInfo, } } + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void WriteMemberSetup(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo, @@ -283,6 +289,7 @@ private void WriteMemberSetup(WriteObjectInfo objectInfo, } // Writes the members of an object + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void WriteMembers(NameInfo memberNameInfo, NameInfo memberTypeNameInfo, object? memberData, @@ -397,6 +404,7 @@ private void WriteMembers(NameInfo memberNameInfo, } // Writes out an array + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void WriteArray(WriteObjectInfo objectInfo, NameInfo? memberNameInfo, WriteObjectInfo? memberObjectInfo) { bool isAllocatedMemberNameInfo = false; @@ -569,6 +577,7 @@ private void WriteArray(WriteObjectInfo objectInfo, NameInfo? memberNameInfo, Wr } // Writes out an array element + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void WriteArrayMember(WriteObjectInfo objectInfo, NameInfo arrayElemTypeNameInfo, object? data) { arrayElemTypeNameInfo._isArrayItem = true; @@ -654,6 +663,7 @@ private void WriteArrayMember(WriteObjectInfo objectInfo, NameInfo arrayElemType } // Iterates over a Rectangle array, for each element of the array invokes WriteArrayMember + [RequiresUnreferencedCode(ObjectWriterUnreferencedCodeMessage)] private void WriteRectangle(WriteObjectInfo objectInfo, int rank, int[] maxA, Array array, NameInfo arrayElemNameTypeInfo, int[]? lowerBoundA) { int[] currentA = new int[rank]; diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs index 07ded6d66157e..628d54d6118a2 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryParser.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Runtime.InteropServices; @@ -11,6 +12,8 @@ namespace System.Runtime.Serialization.Formatters.Binary { internal sealed class BinaryParser { + private const string BinaryParserUnreferencedCodeMessage = "ObjectReader requires unreferenced code"; + private const int ChunkSize = 4096; private static readonly Encoding s_encoding = new UTF8Encoding(false, true); @@ -66,6 +69,7 @@ internal BinaryParser(Stream stream, ObjectReader objectReader) // Reads each record from the input stream. If the record is a primitive type (A number) // then it doesn't have a BinaryHeaderEnum byte. For this case the expected type // has been previously set to Primitive + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] internal void Run() { try @@ -303,6 +307,7 @@ internal void ReadAssembly(BinaryHeaderEnum binaryHeaderEnum) AssemIdToAssemblyTable[record._assemId] = new BinaryAssemblyInfo(record._assemblyString!); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadObject() { if (_binaryObject == null) @@ -373,6 +378,7 @@ private void ReadObject() _objectReader.Parse(pr); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] internal void ReadCrossAppDomainMap() { BinaryCrossAppDomainMap record = new BinaryCrossAppDomainMap(); @@ -395,6 +401,7 @@ internal void ReadCrossAppDomainMap() } } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] internal void ReadObjectWithMap(BinaryHeaderEnum binaryHeaderEnum) { if (_bowm == null) @@ -409,6 +416,7 @@ internal void ReadObjectWithMap(BinaryHeaderEnum binaryHeaderEnum) ReadObjectWithMap(_bowm); } + [RequiresUnreferencedCode("Types might be removed")] private void ReadObjectWithMap(BinaryObjectWithMap record) { BinaryAssemblyInfo? assemblyInfo = null; @@ -495,6 +503,7 @@ private void ReadObjectWithMap(BinaryObjectWithMap record) _objectReader.Parse(pr); } + [RequiresUnreferencedCode("Types might be removed")] internal void ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) { if (_bowmt == null) @@ -509,6 +518,7 @@ internal void ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum) ReadObjectWithMapTyped(_bowmt); } + [RequiresUnreferencedCode("Types might be removed")] private void ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) { BinaryAssemblyInfo? assemblyInfo = null; @@ -589,6 +599,7 @@ private void ReadObjectWithMapTyped(BinaryObjectWithMapTyped record) _objectReader.Parse(pr); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadObjectString(BinaryHeaderEnum binaryHeaderEnum) { if (_objectString == null) @@ -665,6 +676,7 @@ private void ReadObjectString(BinaryHeaderEnum binaryHeaderEnum) _objectReader.Parse(PRs); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadMemberPrimitiveTyped() { if (_memberPrimitiveTyped == null) @@ -712,6 +724,7 @@ private void ReadMemberPrimitiveTyped() _objectReader.Parse(PRs); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadArray(BinaryHeaderEnum binaryHeaderEnum) { BinaryAssemblyInfo? assemblyInfo = null; @@ -898,6 +911,7 @@ private void ReadArrayAsBytes(ParseRecord pr) } } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadMemberPrimitiveUnTyped() { ObjectProgress? objectOp = (ObjectProgress?)_stack.Peek(); @@ -930,6 +944,7 @@ private void ReadMemberPrimitiveUnTyped() _objectReader.Parse(PRs); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadMemberReference() { if (_memberReference == null) @@ -960,6 +975,7 @@ private void ReadMemberReference() _objectReader.Parse(PRs); } + [RequiresUnreferencedCode(BinaryParserUnreferencedCodeMessage)] private void ReadObjectNull(BinaryHeaderEnum binaryHeaderEnum) { if (_objectNull == null) diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs index 9dbe26f1fdd7e..0ccb8f82e2df1 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryTypeConverter.cs @@ -1,8 +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 System.Reflection; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Runtime.Serialization.Formatters.Binary { @@ -191,6 +191,7 @@ internal static object ReadTypeInfo(BinaryTypeEnum binaryTypeEnum, BinaryParser } // Given the wire type information, returns the actual type and additional information + [RequiresUnreferencedCode("Types might be removed")] internal static void TypeFromInfo(BinaryTypeEnum binaryTypeEnum, object? typeInformation, ObjectReader objectReader, diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs index 1a3d907c92bf9..b3b924b12128e 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/ObjectMap.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. -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Runtime.Serialization.Formatters.Binary { @@ -22,7 +22,13 @@ internal sealed class ObjectMap internal int _objectId; internal BinaryAssemblyInfo _assemblyInfo; - internal ObjectMap(string objectName, Type objectType, string[] memberNames, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo) + internal ObjectMap( + string objectName, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + string[] memberNames, + ObjectReader objectReader, + int objectId, + BinaryAssemblyInfo assemblyInfo) { _objectName = objectName; _objectType = objectType; @@ -45,6 +51,7 @@ internal ObjectMap(string objectName, Type objectType, string[] memberNames, Obj } } + [RequiresUnreferencedCode("Types might be removed")] internal ObjectMap(string objectName, string[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, object?[] typeInformationA, int[] memberAssemIds, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) { _objectName = objectName; @@ -99,11 +106,16 @@ internal ReadObjectInfo CreateObjectInfo(ref SerializationInfo? si, ref object?[ // No member type information internal static ObjectMap Create( - string name, Type objectType, string[] memberNames, ObjectReader objectReader, - int objectId, BinaryAssemblyInfo assemblyInfo) => + string name, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type objectType, + string[] memberNames, + ObjectReader objectReader, + int objectId, + BinaryAssemblyInfo assemblyInfo) => new ObjectMap(name, objectType, memberNames, objectReader, objectId, assemblyInfo); // Member type information + [RequiresUnreferencedCode("Types might be removed")] internal static ObjectMap Create( string name, string[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, object?[] typeInformationA, int[] memberAssemIds, ObjectReader objectReader, int objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable) => diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs index 2d3e412fbfaca..c1640a573bfbb 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/IFormatter.cs @@ -1,15 +1,20 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics.CodeAnalysis; using System.IO; namespace System.Runtime.Serialization { public interface IFormatter { + internal const string RequiresUnreferencedCodeMessage = "BinaryFormatter serialization is not trim compatible because the Type of objects being processed cannot be statically discovered."; + [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] object Deserialize(Stream serializationStream); [Obsolete(Obsoletions.BinaryFormatterMessage, DiagnosticId = Obsoletions.BinaryFormatterDiagId, UrlFormat = Obsoletions.SharedUrlFormat)] + [RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)] void Serialize(Stream serializationStream, object graph); ISurrogateSelector? SurrogateSelector { get; set; } SerializationBinder? Binder { get; set; } diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs index c3006025db709..2f46ebc96742e 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/MemberHolder.cs @@ -1,16 +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.Reflection; +using System.Diagnostics.CodeAnalysis; namespace System.Runtime.Serialization { internal sealed class MemberHolder { + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] internal readonly Type _memberType; internal readonly StreamingContext _context; - internal MemberHolder(Type type, StreamingContext ctx) + internal MemberHolder( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, + StreamingContext ctx) { _memberType = type; _context = ctx; diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs index c42125527494e..d9eba9228f1fb 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Reflection; -using System.Globalization; using System.Diagnostics.CodeAnalysis; +using System.Reflection; namespace System.Runtime.Serialization { @@ -15,6 +14,8 @@ public class ObjectManager private const int ArrayMask = MaxArraySize - 1; private const int MaxReferenceDepth = 100; + private const string ObjectManagerUnreferencedCodeMessage = "ObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered."; + private DeserializationEventHandler? _onDeserializationHandler; private SerializationEventHandler? _onDeserializedHandler; @@ -164,6 +165,7 @@ private bool GetCompletionInfo(FixupHolder fixup, [NotNullWhen(true)] out Object return true; } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] private void FixupSpecialObject(ObjectHolder holder) { ISurrogateSelector? uselessSelector = null; @@ -310,9 +312,11 @@ private bool DoValueTypeFixup(FieldInfo? memberToFix, ObjectHolder holder, objec { break; } - if (Nullable.GetUnderlyingType(parentField.FieldType) != null) + + FieldInfo? nullableValueField = GetNullableValueField(parentField.FieldType); + if (nullableValueField != null) { - fieldsTemp[currentFieldIndex] = parentField.FieldType.GetField(nameof(value), BindingFlags.NonPublic | BindingFlags.Instance)!; + fieldsTemp[currentFieldIndex] = nullableValueField; currentFieldIndex++; } @@ -379,6 +383,19 @@ private bool DoValueTypeFixup(FieldInfo? memberToFix, ObjectHolder holder, objec return true; } + [DynamicDependency("value", typeof(Nullable<>))] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", + Justification = "The Nullable.value field will be preserved by the DynamicDependency.")] + private static FieldInfo? GetNullableValueField(Type type) + { + if (Nullable.GetUnderlyingType(type) != null) + { + return type.GetField("value", BindingFlags.NonPublic | BindingFlags.Instance)!; + } + + return null; + } + internal void CompleteObject(ObjectHolder holder, bool bObjectFullyComplete) { FixupHolderList? fixups = holder._missingElements; @@ -613,16 +630,19 @@ private void DoNewlyRegisteredObjectFixups(ObjectHolder holder) return holder.ObjectValue; } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public virtual void RegisterObject(object obj, long objectID) { RegisterObject(obj, objectID, null, 0, null); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public void RegisterObject(object obj, long objectID, SerializationInfo info) { RegisterObject(obj, objectID, info, 0, null); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public void RegisterObject(object obj, long objectID, SerializationInfo? info, long idOfContainingObj, MemberInfo? member) { RegisterObject(obj, objectID, info, idOfContainingObj, member, null); @@ -638,6 +658,7 @@ internal void RegisterString(string? obj, long objectID, SerializationInfo? info return; } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public void RegisterObject(object obj, long objectID, SerializationInfo? info, long idOfContainingObj, MemberInfo? member, int[]? arrayIndex) { if (obj == null) @@ -750,6 +771,7 @@ public void RegisterObject(object obj, long objectID, SerializationInfo? info, l /// The object to be completed. /// The SerializationInfo containing all info for obj. /// The streaming context in which the serialization is taking place. + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] internal void CompleteISerializableObject(object obj, SerializationInfo? info, StreamingContext context) { if (obj == null) @@ -775,7 +797,8 @@ internal void CompleteISerializableObject(object obj, SerializationInfo? info, S constInfo.Invoke(obj, new object?[] { info, context }); } - internal static ConstructorInfo GetDeserializationConstructor(Type t) + internal static ConstructorInfo GetDeserializationConstructor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type t) { foreach (ConstructorInfo ci in t.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { @@ -791,6 +814,7 @@ internal static ConstructorInfo GetDeserializationConstructor(Type t) throw new SerializationException(SR.Format(SR.Serialization_ConstructorNotFound, t.FullName)); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public virtual void DoFixups() { ObjectHolder? temp; @@ -974,18 +998,21 @@ internal virtual void AddOnDeserialization(DeserializationEventHandler handler) _onDeserializationHandler = (DeserializationEventHandler)Delegate.Combine(_onDeserializationHandler, handler); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] internal virtual void AddOnDeserialized(object obj) { SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); _onDeserializedHandler = cache.AddOnDeserialized(obj, _onDeserializedHandler); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] internal virtual void RaiseOnDeserializedEvent(object obj) { SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); cache.InvokeOnDeserialized(obj, _context); } + [RequiresUnreferencedCode(ObjectManagerUnreferencedCodeMessage)] public void RaiseOnDeserializingEvent(object obj) { // Run the OnDeserializing methods diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs index 1bebb7b519208..9e8d8d38f30b6 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationEventsCache.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace System.Runtime.Serialization @@ -15,7 +16,7 @@ internal sealed class SerializationEvents private readonly List? _onDeserializingMethods; private readonly List? _onDeserializedMethods; - internal SerializationEvents(Type? t) + internal SerializationEvents([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? t) { _onSerializingMethods = GetMethodsWithAttribute(typeof(OnSerializingAttribute), t); _onSerializedMethods = GetMethodsWithAttribute(typeof(OnSerializedAttribute), t); @@ -23,7 +24,10 @@ internal SerializationEvents(Type? t) _onDeserializedMethods = GetMethodsWithAttribute(typeof(OnDeserializedAttribute), t); } - private List? GetMethodsWithAttribute(Type attribute, Type? t) + private List? GetMethodsWithAttribute( + Type attribute, + // currently the only way to preserve base, non-public methods is to use All + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type? t) { List? mi = null; @@ -93,7 +97,12 @@ internal static class SerializationEventsCache { private static readonly ConcurrentDictionary s_cache = new ConcurrentDictionary(); - internal static SerializationEvents GetSerializationEventsForType(Type t) => - s_cache.GetOrAdd(t, type => new SerializationEvents(type)); + internal static SerializationEvents GetSerializationEventsForType( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type t) => + s_cache.GetOrAdd(t, type => CreateSerializationEvents(type)); + + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern", + Justification = "The Type is annotated correctly, it just can't pass through the lambda method.")] + private static SerializationEvents CreateSerializationEvents(Type t) => new SerializationEvents(t); } } diff --git a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs index 9e194d4b2cdb4..69ae9935da416 100644 --- a/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs +++ b/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/SerializationObjectManager.cs @@ -2,11 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace System.Runtime.Serialization { public sealed class SerializationObjectManager { + private const string SerializationObjectManagerUnreferencedCodeMessage = "SerializationObjectManager is not trim compatible because the Type of objects being managed cannot be statically discovered."; + private readonly Dictionary _objectSeenTable; // Table to keep track of objects [OnSerializing] has been called on private readonly StreamingContext _context; private SerializationEventHandler? _onSerializedHandler; @@ -17,6 +20,7 @@ public SerializationObjectManager(StreamingContext context) _objectSeenTable = new Dictionary(); } + [RequiresUnreferencedCode(SerializationObjectManagerUnreferencedCodeMessage)] public void RegisterObject(object obj) { // Invoke OnSerializing for this object @@ -38,6 +42,7 @@ public void RegisterObject(object obj) public void RaiseOnSerializedEvent() => _onSerializedHandler?.Invoke(_context); + [RequiresUnreferencedCode(SerializationObjectManagerUnreferencedCodeMessage)] private void AddOnSerialized(object obj) { SerializationEvents cache = SerializationEventsCache.GetSerializationEventsForType(obj.GetType()); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 6c2e4029a2887..40627b6b69dcf 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -18,6 +18,7 @@ protected CriticalHandleZeroOrMinusOneIsInvalid() : base (default(System.IntPtr) } public sealed partial class SafeFileHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public SafeFileHandle() : base (default(bool)) { } public SafeFileHandle(System.IntPtr preexistingHandle, bool ownsHandle) : base (default(bool)) { } public override bool IsInvalid { get { throw null; } } protected override bool ReleaseHandle() { throw null; } @@ -34,6 +35,7 @@ public abstract partial class SafeHandleZeroOrMinusOneIsInvalid : System.Runtime } public sealed partial class SafeWaitHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { + public SafeWaitHandle() : base (default(bool)) { } public SafeWaitHandle(System.IntPtr existingHandle, bool ownsHandle) : base (default(bool)) { } protected override bool ReleaseHandle() { throw null; } } @@ -1693,7 +1695,7 @@ public abstract partial class Delegate : System.ICloneable, System.Runtime.Seria { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The target method might be removed")] protected Delegate(object target, string method) { } - protected Delegate([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type target, string method) { } + protected Delegate([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type target, string method) { } public System.Reflection.MethodInfo Method { get { throw null; } } public object? Target { get { throw null; } } public virtual object Clone() { throw null; } @@ -1712,9 +1714,9 @@ protected Delegate([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAt public static System.Delegate? CreateDelegate(System.Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) { throw null; } public static System.Delegate CreateDelegate(System.Type type, System.Reflection.MethodInfo method) { throw null; } public static System.Delegate? CreateDelegate(System.Type type, System.Reflection.MethodInfo method, bool throwOnBindFailure) { throw null; } - public static System.Delegate CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type target, string method) { throw null; } - public static System.Delegate CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type target, string method, bool ignoreCase) { throw null; } - public static System.Delegate? CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type target, string method, bool ignoreCase, bool throwOnBindFailure) { throw null; } + public static System.Delegate CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type target, string method) { throw null; } + public static System.Delegate CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type target, string method, bool ignoreCase) { throw null; } + public static System.Delegate? CreateDelegate(System.Type type, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type target, string method, bool ignoreCase, bool throwOnBindFailure) { throw null; } public object? DynamicInvoke(params object?[]? args) { throw null; } protected virtual object? DynamicInvokeImpl(object?[]? args) { throw null; } public override bool Equals(object? obj) { throw null; } @@ -2950,7 +2952,7 @@ public abstract partial class MulticastDelegate : System.Delegate { [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The target method might be removed")] protected MulticastDelegate(object target, string method) : base (default(object), default(string)) { } - protected MulticastDelegate([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicMethods | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.NonPublicMethods)] System.Type target, string method) : base (default(object), default(string)) { } + protected MulticastDelegate([System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type target, string method) : base (default(object), default(string)) { } protected sealed override System.Delegate CombineImpl(System.Delegate? follow) { throw null; } public sealed override bool Equals(object? obj) { throw null; } public sealed override int GetHashCode() { throw null; } @@ -3169,8 +3171,12 @@ public Random(int Seed) { } public virtual int Next() { throw null; } public virtual int Next(int maxValue) { throw null; } public virtual int Next(int minValue, int maxValue) { throw null; } + public virtual long NextInt64() { throw null; } + public virtual long NextInt64(long maxValue) { throw null; } + public virtual long NextInt64(long minValue, long maxValue) { throw null; } public virtual void NextBytes(byte[] buffer) { } public virtual void NextBytes(System.Span buffer) { } + public virtual float NextSingle() { throw null; } public virtual double NextDouble() { throw null; } protected virtual double Sample() { throw null; } } diff --git a/src/libraries/System.Runtime/tests/TrimmingTests/CreateDelegateTest.cs b/src/libraries/System.Runtime/tests/TrimmingTests/CreateDelegateTest.cs new file mode 100644 index 0000000000000..91f83fc17b54f --- /dev/null +++ b/src/libraries/System.Runtime/tests/TrimmingTests/CreateDelegateTest.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +/// +/// Tests calling CreateDelegate with a methodName of a non-public method on +/// a base class works correctly in a trimmed app. +/// +class Program +{ + delegate int GetIntDelegate(); + + static int Main() + { + GetIntDelegate d = (GetIntDelegate)Delegate.CreateDelegate(typeof(GetIntDelegate), typeof(Derived), "GetInt"); + + int result = d(); + if (result != 42) + { + return -1; + } + + return 100; + } +} + +class Base +{ + private static int GetInt() => 42; +} + +class Derived : Base { } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs index f14fd4a4f704f..12d2b8700fc14 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/ref/System.Security.Cryptography.Algorithms.cs @@ -11,6 +11,7 @@ public abstract partial class Aes : System.Security.Cryptography.SymmetricAlgori { protected Aes() { } 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.")] public static new System.Security.Cryptography.Aes? Create(string algorithmName) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -125,6 +126,7 @@ public abstract partial class DES : System.Security.Cryptography.SymmetricAlgori protected DES() { } public override byte[] Key { get { throw null; } set { } } public static new System.Security.Cryptography.DES Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.DES? Create(string algName) { throw null; } public static bool IsSemiWeakKey(byte[] rgbKey) { throw null; } public static bool IsWeakKey(byte[] rgbKey) { throw null; } @@ -136,6 +138,7 @@ protected DSA() { } public static new System.Security.Cryptography.DSA Create() { throw null; } public static System.Security.Cryptography.DSA Create(int keySizeInBits) { throw null; } public static System.Security.Cryptography.DSA Create(System.Security.Cryptography.DSAParameters parameters) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.DSA? Create(string algName) { throw null; } public abstract byte[] CreateSignature(byte[] rgbHash); public byte[] CreateSignature(byte[] rgbHash, System.Security.Cryptography.DSASignatureFormat signatureFormat) { throw null; } @@ -287,6 +290,7 @@ protected ECDiffieHellman() { } public static new System.Security.Cryptography.ECDiffieHellman Create() { throw null; } public static System.Security.Cryptography.ECDiffieHellman Create(System.Security.Cryptography.ECCurve curve) { throw null; } public static System.Security.Cryptography.ECDiffieHellman Create(System.Security.Cryptography.ECParameters parameters) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.ECDiffieHellman? Create(string algorithm) { throw null; } public byte[] DeriveKeyFromHash(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { throw null; } public virtual byte[] DeriveKeyFromHash(System.Security.Cryptography.ECDiffieHellmanPublicKey otherPartyPublicKey, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[]? secretPrepend, byte[]? secretAppend) { throw null; } @@ -337,6 +341,7 @@ protected ECDsa() { } public static new System.Security.Cryptography.ECDsa Create() { throw null; } public static System.Security.Cryptography.ECDsa Create(System.Security.Cryptography.ECCurve curve) { throw null; } public static System.Security.Cryptography.ECDsa Create(System.Security.Cryptography.ECParameters parameters) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.ECDsa? Create(string algorithm) { throw null; } public virtual byte[] ExportECPrivateKey() { throw null; } public virtual System.Security.Cryptography.ECParameters ExportExplicitParameters(bool includePrivateParameters) { throw null; } @@ -519,6 +524,7 @@ public abstract partial class MD5 : System.Security.Cryptography.HashAlgorithm { protected MD5() { } public static new System.Security.Cryptography.MD5 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.MD5? Create(string algName) { throw null; } public static byte[] HashData(byte[] source) { throw null; } public static byte[] HashData(System.ReadOnlySpan source) { throw null; } @@ -528,6 +534,7 @@ protected MD5() { } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public partial class PKCS1MaskGenerationMethod : System.Security.Cryptography.MaskGenerationMethod { + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("PKCS1MaskGenerationMethod is not trim compatible because the algorithm implementation referenced by HashName might be removed.")] public PKCS1MaskGenerationMethod() { } public string HashName { get { throw null; } set { } } public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) { throw null; } @@ -536,6 +543,7 @@ public abstract partial class RandomNumberGenerator : System.IDisposable { protected RandomNumberGenerator() { } public static System.Security.Cryptography.RandomNumberGenerator Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static System.Security.Cryptography.RandomNumberGenerator? Create(string rngName) { throw null; } public void Dispose() { } @@ -559,6 +567,7 @@ protected RC2() { } public virtual int EffectiveKeySize { get { throw null; } set { } } public override int KeySize { get { throw null; } set { } } public static new System.Security.Cryptography.RC2 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.RC2? Create(string AlgName) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -586,6 +595,7 @@ public abstract partial class Rijndael : System.Security.Cryptography.SymmetricA { protected Rijndael() { } 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.")] public static new System.Security.Cryptography.Rijndael? Create(string algName) { throw null; } } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] @@ -594,6 +604,7 @@ public sealed partial class RijndaelManaged : System.Security.Cryptography.Rijnd { public RijndaelManaged() { } public override int BlockSize { get { throw null; } set { } } + public override int FeedbackSize { get { throw null; } set { } } public override byte[] IV { get { throw null; } set { } } public override byte[] Key { get { throw null; } set { } } public override int KeySize { get { throw null; } set { } } @@ -617,6 +628,7 @@ protected RSA() { } public static new System.Security.Cryptography.RSA Create() { throw null; } public static System.Security.Cryptography.RSA Create(int keySizeInBits) { throw null; } public static System.Security.Cryptography.RSA Create(System.Security.Cryptography.RSAParameters parameters) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.RSA? Create(string algName) { throw null; } public virtual byte[] Decrypt(byte[] data, System.Security.Cryptography.RSAEncryptionPadding padding) { throw null; } public virtual byte[] DecryptValue(byte[] rgb) { throw null; } @@ -779,6 +791,7 @@ public abstract partial class SHA1 : System.Security.Cryptography.HashAlgorithm { protected SHA1() { } public static new System.Security.Cryptography.SHA1 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.SHA1? Create(string hashName) { throw null; } public static byte[] HashData(byte[] source) { throw null; } public static byte[] HashData(System.ReadOnlySpan source) { throw null; } @@ -800,6 +813,7 @@ public abstract partial class SHA256 : System.Security.Cryptography.HashAlgorith { protected SHA256() { } public static new System.Security.Cryptography.SHA256 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.SHA256? Create(string hashName) { throw null; } public static byte[] HashData(byte[] source) { throw null; } public static byte[] HashData(System.ReadOnlySpan source) { throw null; } @@ -821,6 +835,7 @@ public abstract partial class SHA384 : System.Security.Cryptography.HashAlgorith { protected SHA384() { } public static new System.Security.Cryptography.SHA384 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.SHA384? Create(string hashName) { throw null; } public static byte[] HashData(byte[] source) { throw null; } public static byte[] HashData(System.ReadOnlySpan source) { throw null; } @@ -842,6 +857,7 @@ public abstract partial class SHA512 : System.Security.Cryptography.HashAlgorith { protected SHA512() { } public static new System.Security.Cryptography.SHA512 Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.SHA512? Create(string hashName) { throw null; } public static byte[] HashData(byte[] source) { throw null; } public static byte[] HashData(System.ReadOnlySpan source) { throw null; } @@ -868,8 +884,11 @@ public SignatureDescription(System.Security.SecurityElement el) { } public string? DigestAlgorithm { get { throw null; } set { } } public string? FormatterAlgorithm { get { throw null; } set { } } public string? KeyAlgorithm { get { throw null; } set { } } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("CreateDeformatter is not trim compatible because the algorithm implementation referenced by DeformatterAlgorithm might be removed.")] public virtual System.Security.Cryptography.AsymmetricSignatureDeformatter CreateDeformatter(System.Security.Cryptography.AsymmetricAlgorithm key) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("CreateDigest is not trim compatible because the algorithm implementation referenced by DigestAlgorithm might be removed.")] public virtual System.Security.Cryptography.HashAlgorithm? CreateDigest() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("CreateFormatter is not trim compatible because the algorithm implementation referenced by FormatterAlgorithm might be removed.")] public virtual System.Security.Cryptography.AsymmetricSignatureFormatter CreateFormatter(System.Security.Cryptography.AsymmetricAlgorithm key) { throw null; } } [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] @@ -878,6 +897,7 @@ public abstract partial class TripleDES : System.Security.Cryptography.Symmetric protected TripleDES() { } public override byte[] Key { get { throw null; } set { } } public static new System.Security.Cryptography.TripleDES Create() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] public static new System.Security.Cryptography.TripleDES? Create(string str) { throw null; } public static bool IsWeakKey(byte[] rgbKey) { throw null; } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/ILLink/ILLink.Suppressions.xml b/src/libraries/System.Security.Cryptography.Algorithms/src/ILLink/ILLink.Suppressions.xml deleted file mode 100644 index 6b95ee1ebc93e..0000000000000 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/ILLink/ILLink.Suppressions.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - - - ILLink - IL2026 - member - M:System.Security.Cryptography.Aes.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.DES.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.DSA.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.ECDiffieHellman.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.ECDsa.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.MD5.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.PKCS1MaskGenerationMethod.GenerateMask(System.Byte[],System.Int32) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.RandomNumberGenerator.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.RC2.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.Rijndael.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.RSA.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SHA1.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SHA256.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SHA384.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SHA512.Create(System.String) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SignatureDescription.CreateDeformatter(System.Security.Cryptography.AsymmetricAlgorithm) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SignatureDescription.CreateDigest - - - ILLink - IL2026 - member - M:System.Security.Cryptography.SignatureDescription.CreateFormatter(System.Security.Cryptography.AsymmetricAlgorithm) - - - ILLink - IL2026 - member - M:System.Security.Cryptography.TripleDES.Create(System.String) - - - ILLink - IL2075 - member - M:System.Security.Cryptography.XmlKeyHelper.ParseState.Functions.#cctor - - - ILLink - IL2080 - member - M:System.Security.Cryptography.XmlKeyHelper.ParseState.Functions.#cctor - - - \ No newline at end of file diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj index a34f3a2a6b4d4..3ab0e6157eb7f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System.Security.Cryptography.Algorithms.csproj @@ -1,4 +1,4 @@ - + true $(DefineConstants);INTERNAL_ASYMMETRIC_IMPLEMENTATIONS @@ -12,6 +12,9 @@ SR.SystemSecurityCryptographyAlgorithms_PlatformNotSupported ExcludeApiList.PNSE.Browser.txt + + + diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Aes.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Aes.cs index 09a84f3b568fd..3fdfd83871fc0 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Aes.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Aes.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.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; @@ -25,6 +26,7 @@ protected Aes() return new AesImplementation(); } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new Aes? Create(string algorithmName) { return (Aes?)CryptoConfig.CreateFromName(algorithmName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Common.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Common.cs new file mode 100644 index 0000000000000..5211b8cf65b87 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.Common.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.Security.Cryptography +{ + public partial class CryptoConfig + { + internal const string CreateFromNameUnreferencedCodeMessage = "The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead."; + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs index a1aea6e932dd9..ad088aac44fcf 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/CryptoConfig.cs @@ -1,17 +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.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; -using System.Runtime.InteropServices; namespace System.Security.Cryptography { - public class CryptoConfig + public partial class CryptoConfig { private const string AssemblyName_Cng = "System.Security.Cryptography.Cng"; private const string AssemblyName_Csp = "System.Security.Cryptography.Csp"; @@ -445,7 +444,7 @@ public static void AddAlgorithm(Type algorithm, params string[] names) return retval; } - [RequiresUnreferencedCode("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] + [RequiresUnreferencedCode(CreateFromNameUnreferencedCodeMessage)] public static object? CreateFromName(string name) { return CreateFromName(name, null); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DES.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DES.cs index e9383f6ba4f5d..4f7be0a3944d6 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DES.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DES.cs @@ -3,6 +3,7 @@ using System.Buffers.Binary; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; @@ -26,6 +27,7 @@ protected DES() return new DesImplementation(); } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new DES? Create(string algName) { return (DES?)CryptoConfig.CreateFromName(algName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs index e4fccad9ec08d..e5d90e5d6eadb 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/DSA.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; using System.IO; using System.Runtime.Versioning; @@ -28,6 +29,7 @@ public abstract partial class DSA : AsymmetricAlgorithm protected DSA() { } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new DSA? Create(string algName) { return (DSA?)CryptoConfig.CreateFromName(algName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs index de14abd8089bf..a1661ab9ed701 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDiffieHellman.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.CodeAnalysis; using System.Formats.Asn1; using System.Runtime.Versioning; using System.Security.Cryptography.Asn1; @@ -31,6 +32,7 @@ public override string? SignatureAlgorithm get { return null; } } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new ECDiffieHellman? Create(string algorithm) { if (algorithm == null) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs index 2ca1f5c288e2f..4b46ecc95f697 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/ECDsa.cs @@ -4,6 +4,7 @@ using Internal.Cryptography; using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; using System.IO; using System.Runtime.Versioning; @@ -28,6 +29,7 @@ public abstract partial class ECDsa : AsymmetricAlgorithm protected ECDsa() { } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new ECDsa? Create(string algorithm) { if (algorithm == null) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/MD5.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/MD5.cs index c9623eb1b4ec4..256df522ce6aa 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/MD5.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/MD5.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; @@ -26,6 +27,7 @@ protected MD5() public static new MD5 Create() => new Implementation(); + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new MD5? Create(string algName) => (MD5?)CryptoConfig.CreateFromName(algName); /// diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/PKCS1MaskGenerationMethod.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/PKCS1MaskGenerationMethod.cs index 376a039d7901e..3a20624cfae8e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/PKCS1MaskGenerationMethod.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/PKCS1MaskGenerationMethod.cs @@ -3,8 +3,8 @@ using System.Buffers.Binary; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; -using Internal.Cryptography; namespace System.Security.Cryptography { @@ -14,6 +14,7 @@ public class PKCS1MaskGenerationMethod : MaskGenerationMethod private string _hashNameValue; private const string DefaultHash = "SHA1"; + [RequiresUnreferencedCode("PKCS1MaskGenerationMethod is not trim compatible because the algorithm implementation referenced by HashName might be removed.")] public PKCS1MaskGenerationMethod() { _hashNameValue = DefaultHash; @@ -25,6 +26,9 @@ public string HashName set { _hashNameValue = value ?? DefaultHash; } } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", + Justification = "The constructor of this class is marked as RequiresUnreferencedCode. Don't mark this method as " + + "RequiresUnreferencedCode because it is an override and would then need to mark the base method (and all other overrides) as well.")] public override byte[] GenerateMask(byte[] rgbSeed, int cbReturn) { using (HashAlgorithm? hasher = CryptoConfig.CreateFromName(_hashNameValue) as HashAlgorithm) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RC2.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RC2.cs index 826836e7be695..683325f0bf172 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RC2.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RC2.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; @@ -27,6 +28,7 @@ protected RC2() return new RC2Implementation(); } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new RC2? Create(string AlgName) { return (RC2?)CryptoConfig.CreateFromName(AlgName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs index d488b01600321..2d0919e2f1cd5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RSA.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics.CodeAnalysis; using System.Formats.Asn1; using System.IO; using System.Runtime.InteropServices; @@ -14,6 +15,7 @@ namespace System.Security.Cryptography [UnsupportedOSPlatform("browser")] public abstract partial class RSA : AsymmetricAlgorithm { + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new RSA? Create(string algName) { return (RSA?)CryptoConfig.CreateFromName(algName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs index 8d87e49690aca..a5c708092aac4 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.Versioning; @@ -17,6 +18,7 @@ public static RandomNumberGenerator Create() } [UnsupportedOSPlatform("browser")] + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static RandomNumberGenerator? Create(string rngName) { return (RandomNumberGenerator?)CryptoConfig.CreateFromName(rngName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rijndael.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rijndael.cs index 74bf5f1d7d048..ba7d5c57ca4bd 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rijndael.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/Rijndael.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; @@ -16,6 +17,7 @@ public abstract class Rijndael : SymmetricAlgorithm return new RijndaelImplementation(); } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new Rijndael? Create(string algName) { return (Rijndael?)CryptoConfig.CreateFromName(algName); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA1.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA1.cs index 6e93067c3493f..cc13f6998ea98 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA1.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA1.cs @@ -3,6 +3,7 @@ using Internal.Cryptography; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Security.Cryptography { @@ -22,9 +23,10 @@ protected SHA1() HashSizeValue = HashSizeBits; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "This is the implementaton of SHA1")] + [SuppressMessage("Microsoft.Security", "CA5350", Justification = "This is the implementaton of SHA1")] public static new SHA1 Create() => new Implementation(); + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new SHA1? Create(string hashName) => (SHA1?)CryptoConfig.CreateFromName(hashName); /// diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA256.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA256.cs index eae915be4763d..a0ded322beb5d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA256.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA256.cs @@ -3,6 +3,7 @@ using Internal.Cryptography; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Security.Cryptography { @@ -24,6 +25,7 @@ protected SHA256() public static new SHA256 Create() => new Implementation(); + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new SHA256? Create(string hashName) => (SHA256?)CryptoConfig.CreateFromName(hashName); /// diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA384.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA384.cs index 903af6823a637..273590eae720e 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA384.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA384.cs @@ -3,6 +3,7 @@ using Internal.Cryptography; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Security.Cryptography { @@ -24,6 +25,7 @@ protected SHA384() public static new SHA384 Create() => new Implementation(); + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new SHA384? Create(string hashName) => (SHA384?)CryptoConfig.CreateFromName(hashName); /// diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA512.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA512.cs index d435778e5c590..b36f4c24ccd7d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA512.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SHA512.cs @@ -3,6 +3,7 @@ using Internal.Cryptography; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace System.Security.Cryptography { @@ -24,6 +25,7 @@ protected SHA512() public static new SHA512 Create() => new Implementation(); + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new SHA512? Create(string hashName) => (SHA512?)CryptoConfig.CreateFromName(hashName); /// diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SignatureDescription.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SignatureDescription.cs index 80ce519390924..6ea774a53b41d 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SignatureDescription.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/SignatureDescription.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. -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; namespace System.Security.Cryptography @@ -28,6 +28,7 @@ public SignatureDescription(SecurityElement el) DeformatterAlgorithm = el.SearchForTextOfTag("Deformatter"); } + [RequiresUnreferencedCode("CreateDeformatter is not trim compatible because the algorithm implementation referenced by DeformatterAlgorithm might be removed.")] public virtual AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgorithm key) { AsymmetricSignatureDeformatter? item = (AsymmetricSignatureDeformatter?)CryptoConfig.CreateFromName(DeformatterAlgorithm!); @@ -35,6 +36,7 @@ public virtual AsymmetricSignatureDeformatter CreateDeformatter(AsymmetricAlgori return item; } + [RequiresUnreferencedCode("CreateFormatter is not trim compatible because the algorithm implementation referenced by FormatterAlgorithm might be removed.")] public virtual AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm key) { AsymmetricSignatureFormatter? item = (AsymmetricSignatureFormatter?)CryptoConfig.CreateFromName(FormatterAlgorithm!); @@ -42,6 +44,7 @@ public virtual AsymmetricSignatureFormatter CreateFormatter(AsymmetricAlgorithm return item; } + [RequiresUnreferencedCode("CreateDigest is not trim compatible because the algorithm implementation referenced by DigestAlgorithm might be removed.")] public virtual HashAlgorithm? CreateDigest() { return (HashAlgorithm?)CryptoConfig.CreateFromName(DigestAlgorithm!); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/TripleDES.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/TripleDES.cs index 82589b6636fe5..78a5dffe32d27 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/TripleDES.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/TripleDES.cs @@ -2,12 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Internal.Cryptography; namespace System.Security.Cryptography { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it.")] + [SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for TripleDES, not consuming it.")] [UnsupportedOSPlatform("browser")] public abstract class TripleDES : SymmetricAlgorithm { @@ -25,6 +26,7 @@ protected TripleDES() return new TripleDesImplementation(); } + [RequiresUnreferencedCode(CryptoConfig.CreateFromNameUnreferencedCodeMessage)] public static new TripleDES? Create(string str) { return (TripleDES?)CryptoConfig.CreateFromName(str); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/XmlKeyHelper.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/XmlKeyHelper.cs index be6c871a1b6c7..cb15f49730e26 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/XmlKeyHelper.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/System/Security/Cryptography/XmlKeyHelper.cs @@ -4,6 +4,7 @@ using System.Buffers.Binary; using System.Collections; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text; @@ -264,24 +265,40 @@ internal bool HasElement(string localName) private static class Functions { - private static readonly Type s_xDocument = Type.GetType("System.Xml.Linq.XDocument, System.Private.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51")!; - private static readonly Func s_xDocumentCreate = - s_xDocument.GetMethod( + private const string XmlLinqAssemblyString = ", System.Private.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"; + + private static readonly Func s_xDocumentCreate; + private static readonly PropertyInfo s_docRootProperty; + private static readonly MethodInfo s_getElementsMethod; + private static readonly PropertyInfo s_elementNameProperty; + private static readonly PropertyInfo s_elementValueProperty; + private static readonly PropertyInfo s_nameNameProperty; + +#pragma warning disable CA1810 // explicit static cctor + static Functions() + { + Type xDocument = Type.GetType("System.Xml.Linq.XDocument" + XmlLinqAssemblyString)!; + s_xDocumentCreate = xDocument.GetMethod( "Parse", BindingFlags.Static | BindingFlags.Public, - null, - new[] { typeof(string) }, - null)!.CreateDelegate>(); - private static readonly PropertyInfo s_docRootProperty = s_xDocument.GetProperty("Root")!; - private static readonly MethodInfo s_getElementsMethod = s_docRootProperty.PropertyType.GetMethod( + new[] { typeof(string) })! + .CreateDelegate>(); + + s_docRootProperty = xDocument.GetProperty("Root")!; + + Type xElement = Type.GetType("System.Xml.Linq.XElement" + XmlLinqAssemblyString)!; + s_getElementsMethod = xElement.GetMethod( "Elements", BindingFlags.Instance | BindingFlags.Public, - null, - Type.EmptyTypes, - null)!; - private static readonly PropertyInfo s_elementNameProperty = s_docRootProperty.PropertyType.GetProperty("Name")!; - private static readonly PropertyInfo s_nameNameProperty = s_elementNameProperty.PropertyType.GetProperty("LocalName")!; - private static readonly PropertyInfo s_elementValueProperty = s_docRootProperty.PropertyType.GetProperty("Value")!; + Type.EmptyTypes)!; + + s_elementNameProperty = xElement.GetProperty("Name")!; + s_elementValueProperty = xElement.GetProperty("Value")!; + + Type xName= Type.GetType("System.Xml.Linq.XName" + XmlLinqAssemblyString)!; + s_nameNameProperty = xName.GetProperty("LocalName")!; + } +#pragma warning restore CA1810 internal static object? ParseDocument(string xmlString) => s_docRootProperty.GetValue(s_xDocumentCreate(xmlString)); diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/RSAXmlTest.cs b/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/RSAXmlTest.cs new file mode 100644 index 0000000000000..cf731df2de661 --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/RSAXmlTest.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Security.Cryptography; + +class Program +{ + static int Main(string[] args) + { + using (RSA rsa = RSA.Create()) + { + RSAParameters pubPriv = rsa.ExportParameters(true); + string xmlPriv = rsa.ToXmlString(true); + + using (RSA rsaPriv = RSA.Create()) + { + rsaPriv.FromXmlString(xmlPriv); + + if (!KeyEquals(pubPriv, rsaPriv.ExportParameters(true))) + { + return -1; + } + } + } + + return 100; + } + + private static bool KeyEquals(in RSAParameters expected, in RSAParameters actual) + { + return expected.Modulus.SequenceEqual(actual.Modulus) && + expected.Exponent.SequenceEqual(actual.Exponent) && + expected.P.SequenceEqual(actual.P) && + expected.DP.SequenceEqual(actual.DP) && + expected.Q.SequenceEqual(actual.Q) && + expected.DQ.SequenceEqual(actual.DQ) && + expected.InverseQ.SequenceEqual(actual.InverseQ) && + expected.D.SequenceEqual(actual.D); + } +} diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/System.Security.Cryptography.Algorithms.TrimmingTests.proj b/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/System.Security.Cryptography.Algorithms.TrimmingTests.proj new file mode 100644 index 0000000000000..cbaa647ca3e5a --- /dev/null +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/TrimmingTests/System.Security.Cryptography.Algorithms.TrimmingTests.proj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/libraries/System.Security.Cryptography.OpenSsl/ref/System.Security.Cryptography.OpenSsl.cs b/src/libraries/System.Security.Cryptography.OpenSsl/ref/System.Security.Cryptography.OpenSsl.cs index bf0df9296b4f7..902fa8f3f698a 100644 --- a/src/libraries/System.Security.Cryptography.OpenSsl/ref/System.Security.Cryptography.OpenSsl.cs +++ b/src/libraries/System.Security.Cryptography.OpenSsl/ref/System.Security.Cryptography.OpenSsl.cs @@ -64,6 +64,7 @@ public override void ImportParameters(System.Security.Cryptography.RSAParameters } public sealed partial class SafeEvpPKeyHandle : System.Runtime.InteropServices.SafeHandle { + public SafeEvpPKeyHandle() : base (default(System.IntPtr), default(bool)) { } public SafeEvpPKeyHandle(System.IntPtr handle, bool ownsHandle) : base (default(System.IntPtr), default(bool)) { } public override bool IsInvalid { get { throw null; } } public System.Security.Cryptography.SafeEvpPKeyHandle DuplicateHandle() { throw null; } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCryptMsgHandle.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCryptMsgHandle.cs index c3516ad499de7..4049bdafa919f 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCryptMsgHandle.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeCryptMsgHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCryptMsgHandle : SafeHandle { - private SafeCryptMsgHandle() : + public SafeCryptMsgHandle() : base(IntPtr.Zero, ownsHandle: true) { } diff --git a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeHeapAllocHandle.cs b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeHeapAllocHandle.cs index eedb572911df6..491c86bb9d30c 100644 --- a/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeHeapAllocHandle.cs +++ b/src/libraries/System.Security.Cryptography.Pkcs/src/Microsoft/Win32/SafeHandles/SafeHeapAllocHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeHeapAllocHandle : SafeBuffer, IDisposable { - private SafeHeapAllocHandle() : base(true) { } + public SafeHeapAllocHandle() : base(true) { } internal static SafeHeapAllocHandle Alloc(int size) { diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs b/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs index f4e7a03eb8d6f..8f5f7253b6b6a 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/ref/System.Security.Cryptography.X509Certificates.cs @@ -8,7 +8,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeX509ChainHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid { - internal SafeX509ChainHandle() : base (default(bool)) { } + public SafeX509ChainHandle() : base (default(bool)) { } protected override void Dispose(bool disposing) { } protected override bool ReleaseHandle() { throw null; } } diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.Import.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.Import.cs index fa4bb740a82bb..72b4d63f2bc43 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.Import.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.Import.cs @@ -84,7 +84,7 @@ out pCertContext { if (loadFromFile) rawData = File.ReadAllBytes(fileName!); - pCertContext = FilterPFXStore(rawData!, password, pfxCertStoreFlags); + pCertContext = FilterPFXStore(rawData, password, pfxCertStoreFlags); // If PersistKeySet is set we don't delete the key, so that it persists. // If EphemeralKeySet is set we don't delete the key, because there's no file, so it's a wasteful call. diff --git a/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafeX509ChainHandle.cs b/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafeX509ChainHandle.cs index a115bee34c51d..a6694d94ef986 100644 --- a/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafeX509ChainHandle.cs +++ b/src/libraries/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafeX509ChainHandle.cs @@ -10,7 +10,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed class SafeX509ChainHandle : SafeHandleZeroOrMinusOneIsInvalid { - private SafeX509ChainHandle() + public SafeX509ChainHandle() : base(true) { } diff --git a/src/libraries/System.Security.Principal.Windows/ref/System.Security.Principal.Windows.cs b/src/libraries/System.Security.Principal.Windows/ref/System.Security.Principal.Windows.cs index 5107de52dbf64..5daebe3b72ab7 100644 --- a/src/libraries/System.Security.Principal.Windows/ref/System.Security.Principal.Windows.cs +++ b/src/libraries/System.Security.Principal.Windows/ref/System.Security.Principal.Windows.cs @@ -8,6 +8,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed partial class SafeAccessTokenHandle : System.Runtime.InteropServices.SafeHandle { + public SafeAccessTokenHandle() : base (default(System.IntPtr), default(bool)) { } public SafeAccessTokenHandle(System.IntPtr handle) : base (default(System.IntPtr), default(bool)) { } public static Microsoft.Win32.SafeHandles.SafeAccessTokenHandle InvalidHandle { get { throw null; } } public override bool IsInvalid { get { throw null; } } diff --git a/src/libraries/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeAccessTokenHandle.cs b/src/libraries/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeAccessTokenHandle.cs index 269f6fd68dd04..ccffa35e96e12 100644 --- a/src/libraries/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeAccessTokenHandle.cs +++ b/src/libraries/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeAccessTokenHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { public sealed class SafeAccessTokenHandle : SafeHandle { - private SafeAccessTokenHandle() : base(IntPtr.Zero, true) { } + public SafeAccessTokenHandle() : base(IntPtr.Zero, true) { } // 0 is an Invalid Handle public SafeAccessTokenHandle(IntPtr handle) : base(handle, true) { } diff --git a/src/libraries/System.Security.SecureString/tests/SecureStringTests.cs b/src/libraries/System.Security.SecureString/tests/SecureStringTests.cs index aec5b7faae9aa..ed094b312326c 100644 --- a/src/libraries/System.Security.SecureString/tests/SecureStringTests.cs +++ b/src/libraries/System.Security.SecureString/tests/SecureStringTests.cs @@ -446,8 +446,8 @@ public static void Grow_Large() } [OuterLoop] - [Theory] [InlineData(5)] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void ThreadSafe_Stress(int executionTimeSeconds) // do some minimal verification that an instance can be used concurrently { using (var ss = new SecureString()) diff --git a/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs b/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs index a9ed61540a0cc..48f0392789609 100644 --- a/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs +++ b/src/libraries/System.ServiceProcess.ServiceController/src/Microsoft/Win32/SafeHandles/SafeServiceHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles /// /// Used to wrap handles gotten from OpenSCManager or OpenService /// - internal class SafeServiceHandle : SafeHandle + internal sealed class SafeServiceHandle : SafeHandle { internal SafeServiceHandle(IntPtr handle) : base(IntPtr.Zero, true) { diff --git a/src/libraries/System.Speech/src/Internal/HGlobalSafeHandle.cs b/src/libraries/System.Speech/src/Internal/HGlobalSafeHandle.cs index d7a0ddbd3ce11..ec69855e79e5e 100644 --- a/src/libraries/System.Speech/src/Internal/HGlobalSafeHandle.cs +++ b/src/libraries/System.Speech/src/Internal/HGlobalSafeHandle.cs @@ -12,7 +12,7 @@ internal sealed class HGlobalSafeHandle : SafeHandle { #region Constructors - internal HGlobalSafeHandle() : base(IntPtr.Zero, true) + public HGlobalSafeHandle() : base(IntPtr.Zero, true) { } diff --git a/src/libraries/System.Text.Encoding.CodePages/src/Microsoft/Win32/SafeHandles/SafeAllocHHandle.cs b/src/libraries/System.Text.Encoding.CodePages/src/Microsoft/Win32/SafeHandles/SafeAllocHHandle.cs index 006a3cd98362e..2c07354f17efc 100644 --- a/src/libraries/System.Text.Encoding.CodePages/src/Microsoft/Win32/SafeHandles/SafeAllocHHandle.cs +++ b/src/libraries/System.Text.Encoding.CodePages/src/Microsoft/Win32/SafeHandles/SafeAllocHHandle.cs @@ -9,7 +9,7 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeAllocHHandle : SafeBuffer { - private SafeAllocHHandle() : base(true) { } + public SafeAllocHHandle() : base(true) { } internal SafeAllocHHandle(IntPtr handle) : base(true) { diff --git a/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs index fd8d0b850acd3..2365a92017038 100644 --- a/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs +++ b/src/libraries/System.Text.Json/tests/Serialization/EnumConverterTests.cs @@ -250,7 +250,8 @@ public static void MoreThan64EnumValuesToSerializeWithNamingPolicy() } } - [Fact, OuterLoop] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] + [OuterLoop] public static void VeryLargeAmountOfEnumsToSerialize() { // Ensure we don't throw OutOfMemoryException. diff --git a/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs index a8b319ad27dee..27acb93c63d97 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/Regex.Match.Tests.cs @@ -557,8 +557,9 @@ public void Match_DefaultTimeout_Throws(RegexOptions options) } // On 32-bit we can't test these high inputs as they cause OutOfMemoryExceptions. + // On Linux, we may get killed by the OOM Killer; on Windows, it will swap instead [OuterLoop("Can take several seconds")] - [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess), nameof(PlatformDetection.IsWindows))] [InlineData(@"a\s+", RegexOptions.None)] [InlineData(@"a\s+", RegexOptions.Compiled)] [InlineData(@"a\s+ ", RegexOptions.None)] @@ -571,8 +572,9 @@ public void Match_Timeout_Loop_Throws(string pattern, RegexOptions options) } // On 32-bit we can't test these high inputs as they cause OutOfMemoryExceptions. + // On Linux, we may get killed by the OOM Killer; on Windows, it will swap instead [OuterLoop("Can take several seconds")] - [ConditionalTheory(typeof(Environment), nameof(Environment.Is64BitProcess))] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.Is64BitProcess), nameof(PlatformDetection.IsWindows))] [InlineData(RegexOptions.None)] [InlineData(RegexOptions.Compiled)] public void Match_Timeout_Repetition_Throws(RegexOptions options) diff --git a/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj b/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj index 980ad865134b5..5e469cc4a7c1d 100644 --- a/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj +++ b/src/libraries/System.Threading.Channels/src/System.Threading.Channels.csproj @@ -9,7 +9,8 @@ - + + diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard1.cs similarity index 100% rename from src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard.cs rename to src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard1.cs diff --git a/src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard2.cs b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard2.cs new file mode 100644 index 0000000000000..9c02138e962cb --- /dev/null +++ b/src/libraries/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.netstandard2.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Threading.Tasks; + +namespace System.Threading.Channels +{ + internal partial class AsyncOperation + { + private void UnsafeQueueSetCompletionAndInvokeContinuation() => + ThreadPool.UnsafeQueueUserWorkItem(static s => ((AsyncOperation)s).SetCompletionAndInvokeContinuation(), this); + + private static void QueueUserWorkItem(Action action, object? state) => + Task.Factory.StartNew(action, state, + CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + + private static CancellationTokenRegistration UnsafeRegister(CancellationToken cancellationToken, Action action, object? state) => + cancellationToken.Register(action, state); + } +} diff --git a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/ConcurrentTests.cs b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/ConcurrentTests.cs index fc5e05eac6533..bfa70a2a1a71f 100644 --- a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/ConcurrentTests.cs +++ b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/ConcurrentTests.cs @@ -14,7 +14,7 @@ public class ConcurrentTests static readonly int s_dop = Environment.ProcessorCount * 2; const int IterationCount = 10000; - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // should be a stress test that runs for a while, but needs cleanup public void RunConcurrentTests() { diff --git a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DataflowBlockExtensionTests.cs b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DataflowBlockExtensionTests.cs index a28003d443257..be3d0060b45de 100644 --- a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DataflowBlockExtensionTests.cs +++ b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/DataflowBlockExtensionTests.cs @@ -116,6 +116,7 @@ public void TestNullTarget_Completion() [Fact] [OuterLoop] // finalizer/GC interactions + [ActiveIssue("https://github.com/dotnet/runtime/issues/46566", TestPlatforms.Browser)] public void TestNullTarget_CompletionNoCaching() { // Make sure that the Completion task returned by a NullTarget @@ -343,6 +344,7 @@ public async Task TestAsObservableAndAsObserver_AllObserversGetData() [Fact] [OuterLoop] // stress test + [ActiveIssue("https://github.com/dotnet/runtime/issues/46566", TestPlatforms.Browser)] public void TestAsObservableAndAsObserver_AsObservableDoesntLeak() { const int Count = 1000; @@ -1004,7 +1006,7 @@ public async Task TestReceive_NotYetAvailable() Assert.Equal(expected: 6, actual: await t4); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // timeout involved public async Task TestReceive_Timeout() { diff --git a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformBlockTests.cs b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformBlockTests.cs index 49e0797f36a56..a294870133bef 100644 --- a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformBlockTests.cs +++ b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformBlockTests.cs @@ -312,7 +312,7 @@ public void TestInputCount() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // spins waiting for a condition to be true, though it should happen very quickly public async Task TestCount() { diff --git a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformManyBlockTests.cs b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformManyBlockTests.cs index 1fe86faaa45ac..fd94ffbb16c14 100644 --- a/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformManyBlockTests.cs +++ b/src/libraries/System.Threading.Tasks.Dataflow/tests/Dataflow/TransformManyBlockTests.cs @@ -340,7 +340,7 @@ public void TestInputCount() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // spins waiting for a condition to be true, though it should happen very quickly public async Task TestCount() { diff --git a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs index 8d4029cede1aa..8ba430cbbe5f0 100644 --- a/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/CancellationTokenTests.cs @@ -1469,7 +1469,7 @@ public static async Task CancellationTokenRegistration_ConcurrentUnregisterWithC } [OuterLoop("Runs for several seconds")] - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] public static void Unregister_ConcurrentUse_ThreadSafe() { CancellationTokenRegistration reg = default; diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs index d60add3086151..a140578160d97 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskAPMTest.cs @@ -42,7 +42,7 @@ public sealed class TaskAPMTests : IDisposable /// private const int LongTaskMilliseconds = 100; - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] [InlineData(true)] [InlineData(false)] @@ -110,7 +110,7 @@ public void WaitOnAsyncWaitHandleTechnique(bool hasReturnType) Assert.False(asyncResult.CompletedSynchronously, "Should not have completed synchronously."); } - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] [InlineData(true)] [InlineData(false)] diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskCancelWaitTests.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskCancelWaitTests.cs index df7dc14297a6b..68f74885dae12 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskCancelWaitTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskCancelWaitTests.cs @@ -72,7 +72,7 @@ public static void TaskCancelWait4() TaskCancelWaitTest test = new TaskCancelWaitTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait5() { @@ -106,7 +106,7 @@ public static void TaskCancelWait6() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait7() { @@ -186,7 +186,7 @@ public static void TaskCancelWait10() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait11() { @@ -226,7 +226,7 @@ public static void TaskCancelWait12() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait13() { @@ -262,7 +262,7 @@ public static void TaskCancelWait14() TaskCancelWaitTest test = new TaskCancelWaitTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait15() { @@ -316,7 +316,7 @@ public static void TaskCancelWait17() TaskCancelWaitTest test = new TaskCancelWaitTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait18() { @@ -353,7 +353,7 @@ public static void TaskCancelWait19() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait20() { @@ -465,7 +465,7 @@ public static void TaskCancelWait25() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait26() { @@ -480,7 +480,7 @@ public static void TaskCancelWait26() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait27() { @@ -503,7 +503,7 @@ public static void TaskCancelWait27() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait28() { @@ -530,7 +530,7 @@ public static void TaskCancelWait28() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait29() { @@ -547,7 +547,7 @@ public static void TaskCancelWait29() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait30() { @@ -571,7 +571,7 @@ public static void TaskCancelWait30() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait31() { @@ -583,7 +583,7 @@ public static void TaskCancelWait31() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait32() { @@ -595,7 +595,7 @@ public static void TaskCancelWait32() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait33() { @@ -618,7 +618,7 @@ public static void TaskCancelWait33() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait34() { @@ -642,7 +642,7 @@ public static void TaskCancelWait34() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait35() { @@ -657,7 +657,7 @@ public static void TaskCancelWait35() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait36() { @@ -684,7 +684,7 @@ public static void TaskCancelWait36() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait37() { @@ -701,7 +701,7 @@ public static void TaskCancelWait37() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait38() { @@ -713,7 +713,7 @@ public static void TaskCancelWait38() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait39() { @@ -730,7 +730,7 @@ public static void TaskCancelWait39() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait40() { @@ -754,7 +754,7 @@ public static void TaskCancelWait40() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait41() { @@ -781,7 +781,7 @@ public static void TaskCancelWait41() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait42() { @@ -796,7 +796,7 @@ public static void TaskCancelWait42() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait43() { @@ -819,7 +819,7 @@ public static void TaskCancelWait43() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait44() { @@ -831,7 +831,7 @@ public static void TaskCancelWait44() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait45() { @@ -855,7 +855,7 @@ public static void TaskCancelWait45() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait46() { @@ -870,7 +870,7 @@ public static void TaskCancelWait46() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait47() { @@ -893,7 +893,7 @@ public static void TaskCancelWait47() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait48() { @@ -916,7 +916,7 @@ public static void TaskCancelWait48() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait49() { @@ -928,7 +928,7 @@ public static void TaskCancelWait49() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait50() { @@ -952,7 +952,7 @@ public static void TaskCancelWait50() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait51() { @@ -967,7 +967,7 @@ public static void TaskCancelWait51() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait52() { @@ -994,7 +994,7 @@ public static void TaskCancelWait52() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait53() { @@ -1009,7 +1009,7 @@ public static void TaskCancelWait53() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait54() { @@ -1036,7 +1036,7 @@ public static void TaskCancelWait54() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait55() { @@ -1048,7 +1048,7 @@ public static void TaskCancelWait55() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait56() { @@ -1065,7 +1065,7 @@ public static void TaskCancelWait56() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait57() { @@ -1080,7 +1080,7 @@ public static void TaskCancelWait57() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait58() { @@ -1107,7 +1107,7 @@ public static void TaskCancelWait58() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait59() { @@ -1124,7 +1124,7 @@ public static void TaskCancelWait59() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait60() { @@ -1141,7 +1141,7 @@ public static void TaskCancelWait60() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait61() { @@ -1165,7 +1165,7 @@ public static void TaskCancelWait61() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait62() { @@ -1182,7 +1182,7 @@ public static void TaskCancelWait62() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait63() { @@ -1209,7 +1209,7 @@ public static void TaskCancelWait63() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait64() { @@ -1233,7 +1233,7 @@ public static void TaskCancelWait64() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait65() { @@ -1256,7 +1256,7 @@ public static void TaskCancelWait65() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait66() { @@ -1279,7 +1279,7 @@ public static void TaskCancelWait66() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait67() { @@ -1291,7 +1291,7 @@ public static void TaskCancelWait67() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait68() { @@ -1314,7 +1314,7 @@ public static void TaskCancelWait68() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait69() { @@ -1329,7 +1329,7 @@ public static void TaskCancelWait69() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait70() { @@ -1356,7 +1356,7 @@ public static void TaskCancelWait70() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait71() { @@ -1379,7 +1379,7 @@ public static void TaskCancelWait71() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait72() { @@ -1403,7 +1403,7 @@ public static void TaskCancelWait72() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait73() { @@ -1430,7 +1430,7 @@ public static void TaskCancelWait73() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait74() { @@ -1447,7 +1447,7 @@ public static void TaskCancelWait74() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait75() { @@ -1464,7 +1464,7 @@ public static void TaskCancelWait75() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait76() { @@ -1476,7 +1476,7 @@ public static void TaskCancelWait76() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait77() { @@ -1500,7 +1500,7 @@ public static void TaskCancelWait77() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait78() { @@ -1523,7 +1523,7 @@ public static void TaskCancelWait78() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait79() { @@ -1547,7 +1547,7 @@ public static void TaskCancelWait79() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait80() { @@ -1559,7 +1559,7 @@ public static void TaskCancelWait80() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait81() { @@ -1583,7 +1583,7 @@ public static void TaskCancelWait81() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait82() { @@ -1606,7 +1606,7 @@ public static void TaskCancelWait82() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait83() { @@ -1623,7 +1623,7 @@ public static void TaskCancelWait83() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait84() { @@ -1635,7 +1635,7 @@ public static void TaskCancelWait84() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait85() { @@ -1650,7 +1650,7 @@ public static void TaskCancelWait85() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait86() { @@ -1677,7 +1677,7 @@ public static void TaskCancelWait86() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait87() { @@ -1689,7 +1689,7 @@ public static void TaskCancelWait87() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait88() { @@ -1704,7 +1704,7 @@ public static void TaskCancelWait88() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait89() { @@ -1719,7 +1719,7 @@ public static void TaskCancelWait89() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait90() { @@ -1746,7 +1746,7 @@ public static void TaskCancelWait90() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait91() { @@ -1763,7 +1763,7 @@ public static void TaskCancelWait91() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait92() { @@ -1780,7 +1780,7 @@ public static void TaskCancelWait92() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait93() { @@ -1797,7 +1797,7 @@ public static void TaskCancelWait93() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait94() { @@ -1814,7 +1814,7 @@ public static void TaskCancelWait94() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait95() { @@ -1826,7 +1826,7 @@ public static void TaskCancelWait95() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait96() { @@ -1850,7 +1850,7 @@ public static void TaskCancelWait96() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait97() { @@ -1877,7 +1877,7 @@ public static void TaskCancelWait97() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait98() { @@ -1889,7 +1889,7 @@ public static void TaskCancelWait98() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait99() { @@ -1904,7 +1904,7 @@ public static void TaskCancelWait99() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait100() { @@ -1927,7 +1927,7 @@ public static void TaskCancelWait100() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait101() { @@ -1944,7 +1944,7 @@ public static void TaskCancelWait101() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait102() { @@ -1968,7 +1968,7 @@ public static void TaskCancelWait102() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait103() { @@ -1991,7 +1991,7 @@ public static void TaskCancelWait103() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait104() { @@ -2006,7 +2006,7 @@ public static void TaskCancelWait104() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait105() { @@ -2018,7 +2018,7 @@ public static void TaskCancelWait105() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait106() { @@ -2041,7 +2041,7 @@ public static void TaskCancelWait106() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait107() { @@ -2065,7 +2065,7 @@ public static void TaskCancelWait107() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait108() { @@ -2092,7 +2092,7 @@ public static void TaskCancelWait108() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait109() { @@ -2119,7 +2119,7 @@ public static void TaskCancelWait109() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait110() { @@ -2134,7 +2134,7 @@ public static void TaskCancelWait110() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait111() { @@ -2157,7 +2157,7 @@ public static void TaskCancelWait111() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait112() { @@ -2180,7 +2180,7 @@ public static void TaskCancelWait112() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait113() { @@ -2195,7 +2195,7 @@ public static void TaskCancelWait113() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait114() { @@ -2212,7 +2212,7 @@ public static void TaskCancelWait114() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait115() { @@ -2224,7 +2224,7 @@ public static void TaskCancelWait115() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait116() { @@ -2248,7 +2248,7 @@ public static void TaskCancelWait116() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait117() { @@ -2275,7 +2275,7 @@ public static void TaskCancelWait117() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait118() { @@ -2299,7 +2299,7 @@ public static void TaskCancelWait118() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait119() { @@ -2326,7 +2326,7 @@ public static void TaskCancelWait119() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait120() { @@ -2338,7 +2338,7 @@ public static void TaskCancelWait120() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait121() { @@ -2355,7 +2355,7 @@ public static void TaskCancelWait121() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait122() { @@ -2370,7 +2370,7 @@ public static void TaskCancelWait122() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait123() { @@ -2387,7 +2387,7 @@ public static void TaskCancelWait123() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait124() { @@ -2399,7 +2399,7 @@ public static void TaskCancelWait124() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait125() { @@ -2414,7 +2414,7 @@ public static void TaskCancelWait125() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait126() { @@ -2431,7 +2431,7 @@ public static void TaskCancelWait126() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait127() { @@ -2454,7 +2454,7 @@ public static void TaskCancelWait127() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait128() { @@ -2477,7 +2477,7 @@ public static void TaskCancelWait128() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait129() { @@ -2504,7 +2504,7 @@ public static void TaskCancelWait129() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait130() { @@ -2531,7 +2531,7 @@ public static void TaskCancelWait130() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait131() { @@ -2554,7 +2554,7 @@ public static void TaskCancelWait131() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait132() { @@ -2569,7 +2569,7 @@ public static void TaskCancelWait132() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait133() { @@ -2596,7 +2596,7 @@ public static void TaskCancelWait133() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait134() { @@ -2608,7 +2608,7 @@ public static void TaskCancelWait134() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait135() { @@ -2632,7 +2632,7 @@ public static void TaskCancelWait135() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait136() { @@ -2656,7 +2656,7 @@ public static void TaskCancelWait136() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait137() { @@ -2673,7 +2673,7 @@ public static void TaskCancelWait137() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait138() { @@ -2696,7 +2696,7 @@ public static void TaskCancelWait138() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait139() { @@ -2720,7 +2720,7 @@ public static void TaskCancelWait139() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait140() { @@ -2735,7 +2735,7 @@ public static void TaskCancelWait140() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskCancelWait141() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskContinueWithAllAnyTests.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskContinueWithAllAnyTests.cs index 3b6761d3ece8b..7ac4341cf04b6 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskContinueWithAllAnyTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskContinueWithAllAnyTests.cs @@ -801,7 +801,7 @@ public static void TaskContinueWithAllAnyTest1() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest2() { @@ -817,7 +817,7 @@ public static void TaskContinueWithAllAnyTest2() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest3() { @@ -842,7 +842,7 @@ public static void TaskContinueWithAllAnyTest4() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest5() { @@ -871,7 +871,7 @@ public static void TaskContinueWithAllAnyTest6() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest7() { @@ -898,7 +898,7 @@ public static void TaskContinueWithAllAnyTest8() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest9() { @@ -912,7 +912,7 @@ public static void TaskContinueWithAllAnyTest9() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest10() { @@ -928,7 +928,7 @@ public static void TaskContinueWithAllAnyTest10() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest11() { @@ -968,7 +968,7 @@ public static void TaskContinueWithAllAnyTest13() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest14() { @@ -1008,7 +1008,7 @@ public static void TaskContinueWithAllAnyTest16() } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest17() { @@ -1072,7 +1072,7 @@ public static void TaskContinueWithAllAnyTest21() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest22() { @@ -1099,7 +1099,7 @@ public static void TaskContinueWithAllAnyTest23() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest24() { @@ -1126,7 +1126,7 @@ public static void TaskContinueWithAllAnyTest25() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest26() { @@ -1164,7 +1164,7 @@ public static void TaskContinueWithAllAnyTest27() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest28() { @@ -1185,7 +1185,7 @@ public static void TaskContinueWithAllAnyTest28() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest29() { @@ -1206,7 +1206,7 @@ public static void TaskContinueWithAllAnyTest29() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest30() { @@ -1310,7 +1310,7 @@ public static void TaskContinueWithAllAnyTest37() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest38() { @@ -1323,7 +1323,7 @@ public static void TaskContinueWithAllAnyTest38() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskContinueWithAllAnyTest39() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs index a66fe871f9d8c..e763ae63a43d4 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests.cs @@ -12,7 +12,7 @@ namespace System.Threading.Tasks.Tests { public static class TaskRtTests { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void RunRunTests() { @@ -128,7 +128,7 @@ public static void RunRunTests() Assert.True(future2.Status == TaskStatus.RanToCompletion, " > FAILED. Future(unwrapped) w/ uncanceled token did not end in RanToCompletion state."); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void RunRunTests_Cancellation_Negative() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests_Core.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests_Core.cs index c9a3ca0634153..889b7d75f71c4 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests_Core.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskRtTests_Core.cs @@ -1334,7 +1334,7 @@ public static void RunTaskWaitAnyTests_WithCancellationTokenTests() } // creates a large number of tasks and does WaitAll on them from a thread of the specified apartment state - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void RunTaskWaitAllTests() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskRunSyncTests.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskRunSyncTests.cs index 4a25bf774fd83..d85fe81ffac8f 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskRunSyncTests.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskRunSyncTests.cs @@ -432,7 +432,7 @@ public static void TaskRunSyncTest2() TaskRunSyncTest test = new TaskRunSyncTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest3() { @@ -523,7 +523,7 @@ public static void TaskRunSyncTest14() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest15() { @@ -540,7 +540,7 @@ public static void TaskRunSyncTest16() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest17() { @@ -549,7 +549,7 @@ public static void TaskRunSyncTest17() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest18() { @@ -557,7 +557,7 @@ public static void TaskRunSyncTest18() TaskRunSyncTest test = new TaskRunSyncTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest19() { @@ -566,7 +566,7 @@ public static void TaskRunSyncTest19() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest20() { @@ -590,7 +590,7 @@ public static void TaskRunSyncTest22() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest23() { @@ -598,7 +598,7 @@ public static void TaskRunSyncTest23() TaskRunSyncTest test = new TaskRunSyncTest(parameters); test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskRunSyncTest24() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskStatusTest.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskStatusTest.cs index 7c3a38668a0db..8001e2da4b033 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskStatusTest.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskStatusTest.cs @@ -430,7 +430,7 @@ public static void TaskStatus1() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus2() { @@ -442,7 +442,7 @@ public static void TaskStatus2() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus3() { @@ -503,7 +503,7 @@ public static void TaskStatus7() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus8() { @@ -515,7 +515,7 @@ public static void TaskStatus8() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus9() { @@ -555,7 +555,7 @@ public static void TaskStatus11() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus12() { @@ -570,7 +570,7 @@ public static void TaskStatus12() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus13() { @@ -611,7 +611,7 @@ public static void TaskStatus15() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskStatus16() { diff --git a/src/libraries/System.Threading.Tasks/tests/Task/TaskWaitAllAnyTest.cs b/src/libraries/System.Threading.Tasks/tests/Task/TaskWaitAllAnyTest.cs index c028d596b216d..f597310020d62 100644 --- a/src/libraries/System.Threading.Tasks/tests/Task/TaskWaitAllAnyTest.cs +++ b/src/libraries/System.Threading.Tasks/tests/Task/TaskWaitAllAnyTest.cs @@ -451,7 +451,7 @@ private bool CheckResult(double result) public sealed class TaskWaitAllAny { - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny0() { @@ -462,7 +462,7 @@ public static void TaskWaitAllAny0() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny1() { @@ -504,7 +504,7 @@ public static void TaskWaitAllAny4() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny5() { @@ -531,7 +531,7 @@ public static void TaskWaitAllAny6() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny7() { @@ -807,7 +807,7 @@ public static void TaskWaitAllAny30() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny31() { @@ -892,7 +892,7 @@ public static void TaskWaitAllAny32() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny33() { @@ -903,7 +903,7 @@ public static void TaskWaitAllAny33() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny34() { @@ -940,7 +940,7 @@ public static void TaskWaitAllAny36() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny37() { @@ -1028,7 +1028,7 @@ public static void TaskWaitAllAny44() test.RealRun(); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void TaskWaitAllAny45() { diff --git a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs index d7c963da7b8ea..a0f185089856f 100644 --- a/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs +++ b/src/libraries/System.Threading.Timer/tests/TimerFiringTests.cs @@ -62,7 +62,7 @@ public void Timer_Fires_AndPassesNullStateThroughCallback() } [OuterLoop("Several second delays")] - [Theory] // values chosen based on knowing the 333 pivot used in implementation + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] // values chosen based on knowing the 333 pivot used in implementation [InlineData(1, 1)] [InlineData(50, 50)] [InlineData(250, 50)] @@ -284,7 +284,7 @@ select Task.Run(async () => })); } - [PlatformSpecific(~TestPlatforms.OSX)] // macOS in CI appears to have a lot more variation + [PlatformSpecific(~TestPlatforms.OSX & ~TestPlatforms.Browser)] // macOS and Browser in CI appears to have a lot more variation [OuterLoop("Takes several seconds")] [Theory] // selection based on 333ms threshold used by implementation [InlineData(new int[] { 15 })] diff --git a/src/libraries/System.Threading/tests/BarrierTests.cs b/src/libraries/System.Threading/tests/BarrierTests.cs index 199de37156b61..c6b8640cb58d2 100644 --- a/src/libraries/System.Threading/tests/BarrierTests.cs +++ b/src/libraries/System.Threading/tests/BarrierTests.cs @@ -371,7 +371,7 @@ public static void RunBarrierTest9_PostPhaseException() EnsurePostPhaseThrew(barrier); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void RunBarrierTest10a() { diff --git a/src/libraries/System.Threading/tests/ReaderWriterLockSlimTests.cs b/src/libraries/System.Threading/tests/ReaderWriterLockSlimTests.cs index 2905dbeceb7f6..d4569737e3507 100644 --- a/src/libraries/System.Threading/tests/ReaderWriterLockSlimTests.cs +++ b/src/libraries/System.Threading/tests/ReaderWriterLockSlimTests.cs @@ -380,7 +380,7 @@ public static void WriterToUpgradeableReaderChain() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void ReleaseReadersWhenWaitingWriterTimesOut() { @@ -449,7 +449,7 @@ public static void ReleaseReadersWhenWaitingWriterTimesOut() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void DontReleaseWaitingReadersWhenThereAreWaitingWriters() { diff --git a/src/libraries/System.Threading/tests/SpinLockTests.cs b/src/libraries/System.Threading/tests/SpinLockTests.cs index 5c0ca50420e88..5b027b32a5843 100644 --- a/src/libraries/System.Threading/tests/SpinLockTests.cs +++ b/src/libraries/System.Threading/tests/SpinLockTests.cs @@ -69,7 +69,7 @@ public static void RunSpinLockTests_NegativeTests() /// Number of threads that call enter/exit /// True if succeeded, false otherwise [OuterLoop] - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(2, false)] [InlineData(128, false)] [InlineData(256, false)] @@ -139,7 +139,7 @@ public static void RunSpinLockTest0_Enter(int threadsCount, bool enableThreadIDs /// Number of threads that call enter/exit /// True if succeeded, false otherwise [OuterLoop] - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(2, false)] [InlineData(128, false)] [InlineData(256, false)] @@ -193,7 +193,7 @@ public static void RunSpinLockTest1_TryEnter(int threadsCount, bool enableThread /// Number of threads that call enter/exit /// True if succeeded, false otherwise [OuterLoop] - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(2, false)] [InlineData(128, false)] [InlineData(256, false)] diff --git a/src/libraries/System.Threading/tests/ThreadLocalTests.cs b/src/libraries/System.Threading/tests/ThreadLocalTests.cs index b6a1e1569c89f..f5205eab4831d 100644 --- a/src/libraries/System.Threading/tests/ThreadLocalTests.cs +++ b/src/libraries/System.Threading/tests/ThreadLocalTests.cs @@ -367,7 +367,7 @@ public static void RunThreadLocalTest9_Uninitialized() } } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] public static void ValuesGetterDoesNotThrowUnexpectedExceptionWhenDisposed() { diff --git a/src/libraries/System.Transactions.Local/tests/CloneTxTests.cs b/src/libraries/System.Transactions.Local/tests/CloneTxTests.cs index a90a0e98f7c98..4ce10e8e9eee4 100644 --- a/src/libraries/System.Transactions.Local/tests/CloneTxTests.cs +++ b/src/libraries/System.Transactions.Local/tests/CloneTxTests.cs @@ -157,7 +157,7 @@ public void Run(CloneType cloneType, IsolationLevel isoLevel, bool forcePromote, } [OuterLoop] // transaction timeout of 1.5 seconds per InlineData invocation. - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(CloneType.BlockingDependent, IsolationLevel.Serializable, false, TransactionStatus.Aborted)] [InlineData(CloneType.BlockingDependent, IsolationLevel.RepeatableRead, false, TransactionStatus.Aborted)] [InlineData(CloneType.BlockingDependent, IsolationLevel.ReadCommitted, false, TransactionStatus.Aborted)] diff --git a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs index a4d2e01e77f6d..747d33a280d8c 100644 --- a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs +++ b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs @@ -2148,7 +2148,7 @@ public void PSPENonMsdtcAbortingCloneNotCompleted(bool promote) /// PSPE Non-MSDTC Blocking Clone Completed After Commit. /// [OuterLoop] // long delay - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(false)] [InlineData(true)] public void PSPENonMsdtcBlockingCloneCompletedAfterCommit(bool promote) @@ -2161,7 +2161,7 @@ public void PSPENonMsdtcBlockingCloneCompletedAfterCommit(bool promote) /// PSPE Non-MSDTC Timeout. /// [OuterLoop] // long timeout - [Theory] + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [InlineData(false)] [InlineData(true)] public void PSPENonMsdtcTimeout(bool promote) diff --git a/src/libraries/System.Transactions.Local/tests/TransactionScopeTest.cs b/src/libraries/System.Transactions.Local/tests/TransactionScopeTest.cs index 5a3e93bd32935..06ba17a17d3d3 100644 --- a/src/libraries/System.Transactions.Local/tests/TransactionScopeTest.cs +++ b/src/libraries/System.Transactions.Local/tests/TransactionScopeTest.cs @@ -505,7 +505,7 @@ public void RMFail1() Assert.Null(Transaction.Current); } - [Fact] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsThreadingSupported))] [OuterLoop] // 30 second timeout public void RMFail2() { diff --git a/src/libraries/illink-sharedframework.targets b/src/libraries/illink-sharedframework.targets index ce5cf650c61d8..6e3b2ceeaaa0d 100644 --- a/src/libraries/illink-sharedframework.targets +++ b/src/libraries/illink-sharedframework.targets @@ -4,15 +4,16 @@ AfterTargets="Build" DependsOnTargets="SetCommonILLinkArgs"> - + $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'ILLinkTrimAssembly', '$(BuildSettings)', 'trimmed-runtimepack')) - + $(ILLinkArgs) -c link + $(ILLinkArgs) -u link $(ILLinkArgs) -b true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -135,6 +135,7 @@ <_MonoCMakeArgs Include="-DCMAKE_INSTALL_PREFIX=$(MonoObjDir)out"/> <_MonoCMakeArgs Include="-DCMAKE_INSTALL_LIBDIR=lib"/> <_MonoCMakeArgs Include="-DCMAKE_BUILD_TYPE=$(Configuration)"/> + <_MonoCMakeArgs Condition="'$(CMakeArgs)' != ''" Include="$(CMakeArgs)"/> <_MonoCMakeArgs Condition="'$(MonoEnableLLVM)' == 'true'" Include="-DLLVM_PREFIX=$(MonoLLVMDir)" /> <_MonoCMakeArgs Include="-DGC_SUSPEND=$(MonoThreadSuspend)" /> @@ -247,7 +248,7 @@ - <_MonoCMakeArgs Include="-DENABLE_MINIMAL=ssa,com,jit,portability,assembly_remapping,attach,verifier,appdomains,security,sgen_remset,sgen_marksweep_par,sgen_marksweep_fixed,sgen_marksweep_fixed_par,sgen_copying,logging,remoting,shared_perfcounters,gac,eventpipe" /> + <_MonoCMakeArgs Include="-DENABLE_MINIMAL=ssa,com,jit,portability,assembly_remapping,attach,verifier,appdomains,security,sgen_remset,sgen_marksweep_par,sgen_marksweep_fixed,sgen_marksweep_fixed_par,sgen_copying,logging,remoting,shared_perfcounters,gac" /> <_MonoCMakeArgs Include="-DENABLE_VISIBILITY_HIDDEN=1"/> <_MonoCMakeArgs Include="-DENABLE_LAZY_GC_THREAD_CREATION=1"/> <_MonoCMakeArgs Include="-DENABLE_SIGALTSTACK=0"/> @@ -472,10 +473,10 @@ - + - + $(RuntimeBinDir)libmono-ilgen.a - <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-aot.a"> + <_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true' and '$(BuildMonoAOTCrossCompilerOnly)' != 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-aot.a"> $(RuntimeBinDir)libmono-profiler-aot.a + Condition="'$(MonoGenerateOffsetsOSGroups)' == '' and ('$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true' or '$(TargetsAndroid)' == 'true' or '$(TargetsBrowser)' == 'true')"/> - + diff --git a/src/mono/mono/eglib/garray.c b/src/mono/mono/eglib/garray.c index bc06511189766..97df96736a14f 100644 --- a/src/mono/mono/eglib/garray.c +++ b/src/mono/mono/eglib/garray.c @@ -50,7 +50,7 @@ ensure_capacity (GArrayPriv *priv, guint capacity) if (capacity <= priv->capacity) return; - new_capacity = (capacity + 63) & ~63; + new_capacity = (capacity + (capacity >> 1) + 63) & ~63; priv->array.data = g_realloc (priv->array.data, element_length (priv, new_capacity)); diff --git a/src/mono/mono/eventpipe/ep-rt-mono.c b/src/mono/mono/eventpipe/ep-rt-mono.c index 6c99338dadaf7..07e019e664280 100644 --- a/src/mono/mono/eventpipe/ep-rt-mono.c +++ b/src/mono/mono/eventpipe/ep-rt-mono.c @@ -87,6 +87,11 @@ static const int64_t SECS_TO_100NS = 10000000; static const int64_t SECS_TO_NS = 1000000000; static const int64_t MSECS_TO_MIS = 1000; +/* 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 (HAVE_CLOCK_MONOTONIC) && (defined(TARGET_IOS) || defined(TARGET_OSX) || defined(TARGET_WATCHOS) || defined(TARGET_TVOS)) +#undef HAVE_CLOCK_MONOTONIC +#endif + #ifndef HAVE_CLOCK_MONOTONIC static const int64_t MISECS_TO_NS = 1000; #endif diff --git a/src/mono/mono/metadata/icall-def-netcore.h b/src/mono/mono/metadata/icall-def-netcore.h index 2939685c312bf..5146c88b36606 100644 --- a/src/mono/mono/metadata/icall-def-netcore.h +++ b/src/mono/mono/metadata/icall-def-netcore.h @@ -460,7 +460,6 @@ NOHANDLES(ICALL(ILOCK_2, "Add(long&,long)", ves_icall_System_Threading_Interlock NOHANDLES(ICALL(ILOCK_4, "CompareExchange(double&,double,double)", ves_icall_System_Threading_Interlocked_CompareExchange_Double)) NOHANDLES(ICALL(ILOCK_5, "CompareExchange(int&,int,int)", ves_icall_System_Threading_Interlocked_CompareExchange_Int)) NOHANDLES(ICALL(ILOCK_6, "CompareExchange(int&,int,int,bool&)", ves_icall_System_Threading_Interlocked_CompareExchange_Int_Success)) -NOHANDLES(ICALL(ILOCK_7, "CompareExchange(intptr&,intptr,intptr)", ves_icall_System_Threading_Interlocked_CompareExchange_IntPtr)) NOHANDLES(ICALL(ILOCK_8, "CompareExchange(long&,long,long)", ves_icall_System_Threading_Interlocked_CompareExchange_Long)) NOHANDLES(ICALL(ILOCK_9, "CompareExchange(object&,object&,object&,object&)", ves_icall_System_Threading_Interlocked_CompareExchange_Object)) NOHANDLES(ICALL(ILOCK_10, "CompareExchange(single&,single,single)", ves_icall_System_Threading_Interlocked_CompareExchange_Single)) @@ -468,7 +467,6 @@ NOHANDLES(ICALL(ILOCK_11, "Decrement(int&)", ves_icall_System_Threading_Interloc NOHANDLES(ICALL(ILOCK_12, "Decrement(long&)", ves_icall_System_Threading_Interlocked_Decrement_Long)) NOHANDLES(ICALL(ILOCK_14, "Exchange(double&,double)", ves_icall_System_Threading_Interlocked_Exchange_Double)) NOHANDLES(ICALL(ILOCK_15, "Exchange(int&,int)", ves_icall_System_Threading_Interlocked_Exchange_Int)) -NOHANDLES(ICALL(ILOCK_16, "Exchange(intptr&,intptr)", ves_icall_System_Threading_Interlocked_Exchange_IntPtr)) NOHANDLES(ICALL(ILOCK_17, "Exchange(long&,long)", ves_icall_System_Threading_Interlocked_Exchange_Long)) NOHANDLES(ICALL(ILOCK_18, "Exchange(object&,object&,object&)", ves_icall_System_Threading_Interlocked_Exchange_Object)) NOHANDLES(ICALL(ILOCK_19, "Exchange(single&,single)", ves_icall_System_Threading_Interlocked_Exchange_Single)) diff --git a/src/mono/mono/metadata/jit-icall-reg.h b/src/mono/mono/metadata/jit-icall-reg.h index a9bff58883d7b..637237e28bd04 100644 --- a/src/mono/mono/metadata/jit-icall-reg.h +++ b/src/mono/mono/metadata/jit-icall-reg.h @@ -138,6 +138,7 @@ MONO_JIT_ICALL (mini_llvmonly_resolve_iface_call_gsharedvt) \ MONO_JIT_ICALL (mini_llvmonly_resolve_vcall_gsharedvt) \ MONO_JIT_ICALL (mini_llvmonly_throw_nullref_exception) \ MONO_JIT_ICALL (mini_llvmonly_throw_aot_failed_exception) \ +MONO_JIT_ICALL (mini_llvmonly_pop_lmf) \ MONO_JIT_ICALL (mono_amd64_resume_unwind) \ MONO_JIT_ICALL (mono_amd64_start_gsharedvt_call) \ MONO_JIT_ICALL (mono_amd64_throw_corlib_exception) \ diff --git a/src/mono/mono/metadata/threads-types.h b/src/mono/mono/metadata/threads-types.h index 25860d874d890..97c1fb24f5b3e 100644 --- a/src/mono/mono/metadata/threads-types.h +++ b/src/mono/mono/metadata/threads-types.h @@ -161,8 +161,10 @@ gint64 ves_icall_System_Threading_Interlocked_Exchange_Long(gint64 *location, gi ICALL_EXPORT void ves_icall_System_Threading_Interlocked_Exchange_Object (MonoObject *volatile*location, MonoObject *volatile*value, MonoObject *volatile*res); +#ifndef ENABLE_NETCORE ICALL_EXPORT gpointer ves_icall_System_Threading_Interlocked_Exchange_IntPtr(gpointer *location, gpointer value); +#endif ICALL_EXPORT gfloat ves_icall_System_Threading_Interlocked_Exchange_Single(gfloat *location, gfloat value); @@ -182,8 +184,10 @@ gint64 ves_icall_System_Threading_Interlocked_CompareExchange_Long(gint64 *locat ICALL_EXPORT void ves_icall_System_Threading_Interlocked_CompareExchange_Object (MonoObject *volatile*location, MonoObject *volatile*value, MonoObject *volatile*comparand, MonoObject *volatile*res); +#ifndef ENABLE_NETCORE ICALL_EXPORT gpointer ves_icall_System_Threading_Interlocked_CompareExchange_IntPtr(gpointer *location, gpointer value, gpointer comparand); +#endif ICALL_EXPORT gfloat ves_icall_System_Threading_Interlocked_CompareExchange_Single(gfloat *location, gfloat value, gfloat comparand); diff --git a/src/mono/mono/metadata/threads.c b/src/mono/mono/metadata/threads.c index 7d3875bdf4f3b..1cd6017417428 100644 --- a/src/mono/mono/metadata/threads.c +++ b/src/mono/mono/metadata/threads.c @@ -2617,10 +2617,12 @@ ves_icall_System_Threading_Interlocked_Exchange_Object (MonoObject *volatile*loc mono_gc_wbarrier_generic_nostore_internal ((gpointer)location); // FIXME volatile } +#ifndef ENABLE_NETCORE gpointer ves_icall_System_Threading_Interlocked_Exchange_IntPtr (gpointer *location, gpointer value) { return mono_atomic_xchg_ptr(location, value); } +#endif gfloat ves_icall_System_Threading_Interlocked_Exchange_Single (gfloat *location, gfloat value) { @@ -2686,10 +2688,12 @@ ves_icall_System_Threading_Interlocked_CompareExchange_Object (MonoObject *volat mono_gc_wbarrier_generic_nostore_internal ((gpointer)location); // FIXME volatile } +#ifndef ENABLE_NETCORE gpointer ves_icall_System_Threading_Interlocked_CompareExchange_IntPtr(gpointer *location, gpointer value, gpointer comparand) { return mono_atomic_cas_ptr(location, value, comparand); } +#endif gfloat ves_icall_System_Threading_Interlocked_CompareExchange_Single (gfloat *location, gfloat value, gfloat comparand) { diff --git a/src/mono/mono/mini/aot-compiler.c b/src/mono/mono/mini/aot-compiler.c index 8109fad474326..1438564d942f1 100644 --- a/src/mono/mono/mini/aot-compiler.c +++ b/src/mono/mono/mini/aot-compiler.c @@ -4578,7 +4578,7 @@ add_wrappers (MonoAotCompile *acfg) if ((sig->ret->type == MONO_TYPE_PTR) || (sig->ret->type == MONO_TYPE_TYPEDBYREF)) skip = TRUE; - if (mono_class_is_open_constructed_type (sig->ret)) + if (mono_class_is_open_constructed_type (sig->ret) || m_class_is_byreflike (mono_class_from_mono_type_internal (sig->ret))) skip = TRUE; for (j = 0; j < sig->param_count; j++) { diff --git a/src/mono/mono/mini/aot-runtime.h b/src/mono/mono/mini/aot-runtime.h index 79b9b6d8356b6..101bddba2b1c8 100644 --- a/src/mono/mono/mini/aot-runtime.h +++ b/src/mono/mono/mini/aot-runtime.h @@ -11,7 +11,7 @@ #include "mini.h" /* Version number of the AOT file format */ -#define MONO_AOT_FILE_VERSION 179 +#define MONO_AOT_FILE_VERSION 180 #define MONO_AOT_TRAMP_PAGE_SIZE 16384 diff --git a/src/mono/mono/mini/llvmonly-runtime.c b/src/mono/mono/mini/llvmonly-runtime.c index 0068a280418d2..2b25a48f4b771 100644 --- a/src/mono/mono/mini/llvmonly-runtime.c +++ b/src/mono/mono/mini/llvmonly-runtime.c @@ -815,3 +815,15 @@ mini_llvmonly_throw_aot_failed_exception (const char *name) g_free (msg); mono_llvm_throw_exception ((MonoObject*)ex); } + +/* + * mini_llvmonly_pop_lmf: + * + * Pop LMF off the LMF stack. + */ +void +mini_llvmonly_pop_lmf (MonoLMF *lmf) +{ + if (lmf->previous_lmf) + mono_set_lmf ((MonoLMF*)lmf->previous_lmf); +} diff --git a/src/mono/mono/mini/llvmonly-runtime.h b/src/mono/mono/mini/llvmonly-runtime.h index 35aa4dc16a864..6623b79883a77 100644 --- a/src/mono/mono/mini/llvmonly-runtime.h +++ b/src/mono/mono/mini/llvmonly-runtime.h @@ -35,4 +35,6 @@ G_EXTERN_C void mini_llvmonly_throw_nullref_exception (void); G_EXTERN_C void mini_llvmonly_throw_aot_failed_exception (const char *name); +G_EXTERN_C void mini_llvmonly_pop_lmf (MonoLMF *lmf); + #endif diff --git a/src/mono/mono/mini/memory-access.c b/src/mono/mono/mini/memory-access.c index 52713030c5214..b666d4c4e81a9 100644 --- a/src/mono/mono/mini/memory-access.c +++ b/src/mono/mono/mini/memory-access.c @@ -502,6 +502,8 @@ mini_emit_memory_store (MonoCompile *cfg, MonoType *type, MonoInst *dest, MonoIn } else if (!mini_debug_options.weak_memory_model && mini_type_is_reference (type) && cfg->method->wrapper_type != MONO_WRAPPER_WRITE_BARRIER) mini_emit_memory_barrier (cfg, MONO_MEMORY_BARRIER_REL); + MONO_EMIT_NULL_CHECK (cfg, dest->dreg, FALSE); + if ((ins_flag & MONO_INST_UNALIGNED) && !COMPILE_LLVM (cfg)) { MonoInst *addr, *mov, *tmp_var; diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index 30ba875ef9871..681ca19de2e37 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -8050,6 +8050,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b #ifdef TARGET_WASM if (common_call && needs_stack_walk) + /* If an exception is thrown, the LMF is popped by a call to mini_llvmonly_pop_lmf () */ emit_pop_lmf (cfg); #endif @@ -11667,6 +11668,17 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b mono_get_got_var (cfg); #endif +#ifdef TARGET_WASM + if (cfg->lmf_var) { + // mini_llvmonly_pop_lmf () might be called before emit_push_lmf () so initialize the LMF + cfg->cbb = init_localsbb; + EMIT_NEW_VARLOADA (cfg, ins, cfg->lmf_var, NULL); + int lmf_reg = ins->dreg; + + EMIT_NEW_STORE_MEMBASE (cfg, ins, OP_STORE_MEMBASE_IMM, lmf_reg, MONO_STRUCT_OFFSET (MonoLMF, previous_lmf), 0); + } +#endif + if (cfg->method == method && cfg->got_var) mono_emit_load_got_addr (cfg); diff --git a/src/mono/mono/mini/mini-llvm-cpp.cpp b/src/mono/mono/mini/mini-llvm-cpp.cpp index dd9138ef6c288..7240b067bdf61 100644 --- a/src/mono/mono/mini/mini-llvm-cpp.cpp +++ b/src/mono/mono/mini/mini-llvm-cpp.cpp @@ -61,7 +61,15 @@ mono_llvm_dump_module (LLVMModuleRef module) { /* Same as LLVMDumpModule (), but print to stdout */ fflush (stdout); - outs () << (*unwrap (module)); + outs () << (*unwrap (module)) << "\n"; + outs ().flush (); +} + +void +mono_llvm_dump_type (LLVMTypeRef type) +{ + fflush (stdout); + outs () << (*unwrap (type)) << "\n"; outs ().flush (); } diff --git a/src/mono/mono/mini/mini-llvm-cpp.h b/src/mono/mono/mini/mini-llvm-cpp.h index 9173b6341e4f6..b3288b224faee 100644 --- a/src/mono/mono/mini/mini-llvm-cpp.h +++ b/src/mono/mono/mini/mini-llvm-cpp.h @@ -64,6 +64,9 @@ mono_llvm_dump_value (LLVMValueRef value); void mono_llvm_dump_module (LLVMModuleRef module); +void +mono_llvm_dump_type (LLVMTypeRef type); + LLVMValueRef mono_llvm_build_alloca (LLVMBuilderRef builder, LLVMTypeRef Ty, LLVMValueRef ArraySize, diff --git a/src/mono/mono/mini/mini-llvm.c b/src/mono/mono/mini/mini-llvm.c index c930af2e88b2b..74d7e38f86ae7 100644 --- a/src/mono/mono/mini/mini-llvm.c +++ b/src/mono/mono/mini/mini-llvm.c @@ -1228,7 +1228,7 @@ convert_full (EmitContext *ctx, LLVMValueRef v, LLVMTypeRef dtype, gboolean is_u return LLVMBuildBitCast (ctx->builder, v, dtype, ""); mono_llvm_dump_value (v); - mono_llvm_dump_value (LLVMConstNull (dtype)); + mono_llvm_dump_type (dtype); printf ("\n"); g_assert_not_reached (); return NULL; @@ -2593,8 +2593,9 @@ build_alloca_llvm_type (EmitContext *ctx, LLVMTypeRef t, int align) return build_alloca_llvm_type_name (ctx, t, align, ""); } + static LLVMValueRef -build_alloca (EmitContext *ctx, MonoType *t) +build_named_alloca (EmitContext *ctx, MonoType *t, char const *name) { MonoClass *k = mono_class_from_mono_type_internal (t); int align; @@ -2610,7 +2611,13 @@ build_alloca (EmitContext *ctx, MonoType *t) while (mono_is_power_of_two (align) == -1) align ++; - return build_alloca_llvm_type (ctx, type_to_llvm_type (ctx, t), align); + return build_alloca_llvm_type_name (ctx, type_to_llvm_type (ctx, t), align, name); +} + +static LLVMValueRef +build_alloca (EmitContext *ctx, MonoType *t) +{ + return build_named_alloca (ctx, t, ""); } static LLVMValueRef @@ -3599,9 +3606,9 @@ emit_entry_bb (EmitContext *ctx, LLVMBuilderRef builder) /* Could be already created by an OP_VPHI */ if (!ctx->addresses [var->dreg]) { if (var->flags & MONO_INST_LMF) - ctx->addresses [var->dreg] = build_alloca_llvm_type (ctx, LLVMArrayType (LLVMInt8Type (), MONO_ABI_SIZEOF (MonoLMF)), sizeof (target_mgreg_t)); + ctx->addresses [var->dreg] = build_alloca_llvm_type_name (ctx, LLVMArrayType (LLVMInt8Type (), MONO_ABI_SIZEOF (MonoLMF)), sizeof (target_mgreg_t), "entry_lmf"); else - ctx->addresses [var->dreg] = build_alloca (ctx, var->inst_vtype); + ctx->addresses [var->dreg] = build_named_alloca (ctx, var->inst_vtype, "entry"); //LLVMSetValueName (ctx->addresses [var->dreg], g_strdup_printf ("vreg_loc_%d", var->dreg)); } ctx->vreg_cli_types [var->dreg] = var->inst_vtype; @@ -3721,6 +3728,7 @@ emit_entry_bb (EmitContext *ctx, LLVMBuilderRef builder) switch (ainfo->storage) { case LLVMArgVtypeInReg: case LLVMArgVtypeByVal: + case LLVMArgAsIArgs: #ifdef ENABLE_NETCORE // FIXME: Enabling this fails on windows case LLVMArgVtypeAddr: @@ -3729,7 +3737,7 @@ emit_entry_bb (EmitContext *ctx, LLVMBuilderRef builder) { if (MONO_CLASS_IS_SIMD (ctx->cfg, mono_class_from_mono_type_internal (ainfo->type))) /* Treat these as normal values */ - ctx->values [reg] = LLVMBuildLoad (builder, ctx->addresses [reg], ""); + ctx->values [reg] = LLVMBuildLoad (builder, ctx->addresses [reg], "simd_vtype"); break; } default: @@ -4314,6 +4322,8 @@ process_call (EmitContext *ctx, MonoBasicBlock *bb, LLVMBuilderRef *builder_ref, if (!addresses [call->inst.dreg]) addresses [call->inst.dreg] = build_alloca (ctx, sig->ret); LLVMBuildStore (builder, lcall, convert_full (ctx, addresses [call->inst.dreg], LLVMPointerType (LLVMTypeOf (lcall), 0), FALSE)); + if (MONO_CLASS_IS_SIMD (ctx->cfg, mono_class_from_mono_type_internal (sig->ret))) + values [ins->dreg] = LLVMBuildBitCast(builder, lcall, type_to_llvm_type (ctx, sig->ret), "callret_cvt_simd"); break; case LLVMArgVtypeRetAddr: case LLVMArgVtypeByRef: @@ -4700,6 +4710,24 @@ emit_llvmonly_handler_start (EmitContext *ctx, MonoBasicBlock *bb, LLVMBasicBloc mono_llvm_emit_clear_exception_call (ctx, ctx->builder); } +#ifdef TARGET_WASM + if (ctx->cfg->lmf_var) { + LLVMValueRef callee; + LLVMValueRef args [1]; + LLVMTypeRef sig = LLVMFunctionType1 (LLVMVoidType (), ctx->module->ptr_type, FALSE); + + /* + * There might be an LMF on the stack inserted to enable stack walking, see + * method_needs_stack_walk (). If an exception is thrown, the LMF popping code + * is not executed, so do it here. + */ + g_assert (ctx->addresses [ctx->cfg->lmf_var->dreg]); + callee = get_callee (ctx, sig, MONO_PATCH_INFO_JIT_ICALL_ADDR, GUINT_TO_POINTER (MONO_JIT_ICALL_mini_llvmonly_pop_lmf)); + args [0] = convert (ctx, ctx->addresses [ctx->cfg->lmf_var->dreg], ctx->module->ptr_type); + emit_call (ctx, bb, &ctx->builder, callee, args, 1); + } +#endif + LLVMBuilderRef handler_builder = create_builder (ctx); LLVMBasicBlockRef target_bb = ctx->bblocks [bb->block_num].call_handler_target_bb; LLVMPositionBuilderAtEnd (handler_builder, target_bb); @@ -5201,10 +5229,12 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) case LLVMArgVtypeAsScalar: { LLVMTypeRef ret_type = LLVMGetReturnType (LLVMGetElementType (LLVMTypeOf (method))); LLVMValueRef retval; - - g_assert (addresses [ins->sreg1]); - - retval = LLVMBuildLoad (builder, LLVMBuildBitCast (builder, addresses [ins->sreg1], LLVMPointerType (ret_type, 0), ""), ""); + if (MONO_CLASS_IS_SIMD (ctx->cfg, mono_class_from_mono_type_internal (sig->ret))) + retval = LLVMBuildBitCast (builder, values [ins->sreg1], ret_type, "setret_cvt_simd"); + else { + g_assert (addresses [ins->sreg1]); + retval = LLVMBuildLoad (builder, LLVMBuildBitCast (builder, addresses [ins->sreg1], LLVMPointerType (ret_type, 0), ""), ""); + } LLVMBuildRet (builder, retval); break; } @@ -6185,8 +6215,9 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) break; case OP_LDADDR: { MonoInst *var = ins->inst_i0; + MonoClass *klass = var->klass; - if (var->opcode == OP_VTARG_ADDR) { + if (var->opcode == OP_VTARG_ADDR && !MONO_CLASS_IS_SIMD(cfg, klass)) { /* The variable contains the vtype address */ values [ins->dreg] = values [var->dreg]; } else if (var->opcode == OP_GSHAREDVT_LOCAL) { @@ -6755,7 +6786,7 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) } if (!addresses [ins->dreg]) - addresses [ins->dreg] = build_alloca (ctx, m_class_get_byval_arg (klass)); + addresses [ins->dreg] = build_named_alloca (ctx, m_class_get_byval_arg (klass), "vzero"); LLVMValueRef ptr = LLVMBuildBitCast (builder, addresses [ins->dreg], LLVMPointerType (LLVMInt8Type (), 0), ""); emit_memset (ctx, builder, ptr, const_int32 (mono_class_value_size (klass, NULL)), 0); break; @@ -6922,7 +6953,31 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) /* * SIMD */ -#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_WASM) +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_WASM) + case OP_EXPAND_I1: + case OP_EXPAND_I2: + case OP_EXPAND_I4: + case OP_EXPAND_I8: + case OP_EXPAND_R4: + case OP_EXPAND_R8: { + LLVMTypeRef t; + LLVMValueRef mask [32], v; + int i; + +#ifdef ENABLE_NETCORE + t = simd_class_to_llvm_type (ctx, ins->klass); +#else + t = simd_op_to_llvm_type (ins->opcode); +#endif + for (i = 0; i < 32; ++i) + mask [i] = LLVMConstInt (LLVMInt32Type (), 0, FALSE); + + v = convert (ctx, values [ins->sreg1], LLVMGetElementType (t)); + + values [ins->dreg] = LLVMBuildInsertElement (builder, LLVMConstNull (t), v, LLVMConstInt (LLVMInt32Type (), 0, FALSE), ""); + values [ins->dreg] = LLVMBuildShuffleVector (builder, values [ins->dreg], LLVMGetUndef (t), LLVMConstVector (mask, LLVMGetVectorSize (t)), ""); + break; + } case OP_XZERO: { values [ins->dreg] = LLVMConstNull (type_to_llvm_type (ctx, m_class_get_byval_arg (ins->klass))); break; @@ -6943,6 +6998,9 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) mono_llvm_build_aligned_store (builder, values [ins->sreg1], dest, FALSE, 1); break; } +#endif // defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_WASM) + +#if defined(TARGET_X86) || defined(TARGET_AMD64) || defined(TARGET_WASM) case OP_PADDB: case OP_PADDW: case OP_PADDD: @@ -7177,31 +7235,6 @@ process_bb (EmitContext *ctx, MonoBasicBlock *bb) break; } - case OP_EXPAND_I1: - case OP_EXPAND_I2: - case OP_EXPAND_I4: - case OP_EXPAND_I8: - case OP_EXPAND_R4: - case OP_EXPAND_R8: { - LLVMTypeRef t; - LLVMValueRef mask [32], v; - int i; - -#ifdef ENABLE_NETCORE - t = simd_class_to_llvm_type (ctx, ins->klass); -#else - t = simd_op_to_llvm_type (ins->opcode); -#endif - for (i = 0; i < 32; ++i) - mask [i] = LLVMConstInt (LLVMInt32Type (), 0, FALSE); - - v = convert (ctx, values [ins->sreg1], LLVMGetElementType (t)); - - values [ins->dreg] = LLVMBuildInsertElement (builder, LLVMConstNull (t), v, LLVMConstInt (LLVMInt32Type (), 0, FALSE), ""); - values [ins->dreg] = LLVMBuildShuffleVector (builder, values [ins->dreg], LLVMGetUndef (t), LLVMConstVector (mask, LLVMGetVectorSize (t)), ""); - break; - } - case OP_INSERT_I1: values [ins->dreg] = LLVMBuildInsertElement (builder, values [ins->sreg1], convert (ctx, values [ins->sreg2], LLVMInt8Type ()), LLVMConstInt (LLVMInt32Type (), ins->inst_c0, FALSE), dname); break; @@ -9593,6 +9626,7 @@ emit_method_inner (EmitContext *ctx) linfo->rgctx_arg = TRUE; else if (needs_extra_arg (ctx, cfg->method)) linfo->dummy_arg = TRUE; + ctx->method_type = method_type = sig_to_llvm_sig_full (ctx, sig, linfo); if (!ctx_ok (ctx)) return; diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 21f21ee15f484..903b3a0c630de 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -4913,6 +4913,7 @@ register_icalls (void) register_icall (mini_llvmonly_init_delegate_virtual, mono_icall_sig_void_object_object_ptr, TRUE); register_icall (mini_llvmonly_throw_nullref_exception, mono_icall_sig_void, TRUE); register_icall (mini_llvmonly_throw_aot_failed_exception, mono_icall_sig_void_ptr, TRUE); + register_icall (mini_llvmonly_pop_lmf, mono_icall_sig_void_ptr, TRUE); register_icall (mono_get_assembly_object, mono_icall_sig_object_ptr, TRUE); register_icall (mono_get_method_object, mono_icall_sig_object_ptr, TRUE); diff --git a/src/mono/mono/mini/mini.h b/src/mono/mono/mini/mini.h index 1461c94827c58..f86908f6a0116 100644 --- a/src/mono/mono/mini/mini.h +++ b/src/mono/mono/mini/mini.h @@ -314,8 +314,9 @@ enum { #define MONO_IS_ZERO(ins) (((ins)->opcode == OP_VZERO) || ((ins)->opcode == OP_XZERO)) #ifdef TARGET_ARM64 -// FIXME: enable for Arm64 -#define MONO_CLASS_IS_SIMD(cfg, klass) (0) +// SIMD is only supported on arm64 when using the LLVM backend. When not using +// the LLVM backend, treat SIMD datatypes as regular value types. +#define MONO_CLASS_IS_SIMD(cfg, klass) ( ((cfg)->opt & MONO_OPT_SIMD) && ( COMPILE_LLVM (cfg) ) && m_class_is_simd_type (klass) ) #else #define MONO_CLASS_IS_SIMD(cfg, klass) (((cfg)->opt & MONO_OPT_SIMD) && m_class_is_simd_type (klass)) #endif diff --git a/src/mono/mono/mini/simd-intrinsics-netcore.c b/src/mono/mono/mini/simd-intrinsics-netcore.c index 1646345539cdd..f62512feb75a6 100644 --- a/src/mono/mono/mini/simd-intrinsics-netcore.c +++ b/src/mono/mono/mini/simd-intrinsics-netcore.c @@ -267,7 +267,33 @@ get_vector_t_elem_type (MonoType *vector_type) return etype; } -#ifdef TARGET_AMD64 +static MonoInst * +emit_arch_vector128_create_multi (MonoCompile *cfg, MonoMethodSignature *fsig, MonoClass *klass, MonoType *etype, MonoInst **args) +{ +#if defined(TARGET_AMD64) + MonoInst *ins, *load; + + // FIXME: Optimize this + MONO_INST_NEW (cfg, ins, OP_LOCALLOC_IMM); + ins->dreg = alloc_preg (cfg); + ins->inst_imm = 16; + MONO_ADD_INS (cfg->cbb, ins); + + int esize = mono_class_value_size (mono_class_from_mono_type_internal (etype), NULL); + int store_opcode = mono_type_to_store_membase (cfg, etype); + for (int i = 0; i < fsig->param_count; ++i) + MONO_EMIT_NEW_STORE_MEMBASE (cfg, store_opcode, ins->dreg, i * esize, args [i]->dreg); + + load = emit_simd_ins (cfg, klass, OP_SSE_LOADU, ins->dreg, -1); + load->inst_c0 = 16; + load->inst_c1 = get_underlying_type (etype); + return load; +#else + return NULL; +#endif +} + +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) static int type_to_expand_op (MonoType *type) @@ -294,6 +320,69 @@ type_to_expand_op (MonoType *type) } } +static guint16 vector_128_methods [] = { + SN_AsByte, + SN_AsDouble, + SN_AsInt16, + SN_AsInt32, + SN_AsInt64, + SN_AsSByte, + SN_AsSingle, + SN_AsUInt16, + SN_AsUInt32, + SN_AsUInt64, + SN_Create, + SN_CreateScalarUnsafe, +}; + +static MonoInst* +emit_vector128 (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) +{ + if (!COMPILE_LLVM (cfg)) + return NULL; + + MonoClass *klass = cmethod->klass; + int id = lookup_intrins (vector_128_methods, sizeof (vector_128_methods), cmethod); + if (id == -1) + return NULL; + + if (!strcmp (m_class_get_name (cfg->method->klass), "Vector256")) + return NULL; // TODO: Fix Vector256.WithUpper/WithLower + + MonoTypeEnum arg0_type = fsig->param_count > 0 ? get_underlying_type (fsig->params [0]) : MONO_TYPE_VOID; + + switch (id) { + case SN_AsByte: + case SN_AsDouble: + case SN_AsInt16: + case SN_AsInt32: + case SN_AsInt64: + case SN_AsSByte: + case SN_AsSingle: + case SN_AsUInt16: + case SN_AsUInt32: + case SN_AsUInt64: + return emit_simd_ins (cfg, klass, OP_XCAST, args [0]->dreg, -1); + case SN_Create: { + MonoType *etype = get_vector_t_elem_type (fsig->ret); + if (fsig->param_count == 1 && mono_metadata_type_equal (fsig->params [0], etype)) + return emit_simd_ins (cfg, klass, type_to_expand_op (etype), args [0]->dreg, -1); + else + return emit_arch_vector128_create_multi (cfg, fsig, klass, etype, args); + } + case SN_CreateScalarUnsafe: + return emit_simd_ins_for_sig (cfg, klass, OP_CREATE_SCALAR_UNSAFE, -1, arg0_type, fsig, args); + default: + break; + } + + return NULL; +} + +#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64) + +#ifdef TARGET_AMD64 + static guint16 vector_methods [] = { SN_ConvertToDouble, SN_ConvertToInt32, @@ -702,7 +791,7 @@ emit_sys_numerics_vector_t (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSig return NULL; } -#endif // !TARGET_ARM64 +#endif // TARGET_AMD64 static MonoInst* emit_invalid_operation (MonoCompile *cfg, const char* message) @@ -714,6 +803,7 @@ emit_invalid_operation (MonoCompile *cfg, const char* message) #ifdef TARGET_ARM64 + static SimdIntrinsic armbase_methods [] = { {SN_LeadingSignCount}, {SN_LeadingZeroCount}, @@ -1845,90 +1935,11 @@ emit_x86_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature return NULL; } -static guint16 vector_128_methods [] = { - SN_AsByte, - SN_AsDouble, - SN_AsInt16, - SN_AsInt32, - SN_AsInt64, - SN_AsSByte, - SN_AsSingle, - SN_AsUInt16, - SN_AsUInt32, - SN_AsUInt64, - SN_Create, - SN_CreateScalarUnsafe, -}; - static guint16 vector_128_t_methods [] = { SN_get_Count, SN_get_Zero, }; -static MonoInst* -emit_vector128 (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) -{ - MonoClass *klass; - int id; - - if (!COMPILE_LLVM (cfg)) - return NULL; - - klass = cmethod->klass; - id = lookup_intrins (vector_128_methods, sizeof (vector_128_methods), cmethod); - if (id == -1) - return NULL; - - if (!strcmp (m_class_get_name (cfg->method->klass), "Vector256")) - return NULL; // TODO: Fix Vector256.WithUpper/WithLower - - MonoTypeEnum arg0_type = fsig->param_count > 0 ? get_underlying_type (fsig->params [0]) : MONO_TYPE_VOID; - - switch (id) { - case SN_AsByte: - case SN_AsDouble: - case SN_AsInt16: - case SN_AsInt32: - case SN_AsInt64: - case SN_AsSByte: - case SN_AsSingle: - case SN_AsUInt16: - case SN_AsUInt32: - case SN_AsUInt64: - return emit_simd_ins (cfg, klass, OP_XCAST, args [0]->dreg, -1); - case SN_Create: { - MonoType *etype = get_vector_t_elem_type (fsig->ret); - if (fsig->param_count == 1 && mono_metadata_type_equal (fsig->params [0], etype)) { - return emit_simd_ins (cfg, klass, type_to_expand_op (etype), args [0]->dreg, -1); - } else { - MonoInst *ins, *load; - - // FIXME: Optimize this - MONO_INST_NEW (cfg, ins, OP_LOCALLOC_IMM); - ins->dreg = alloc_preg (cfg); - ins->inst_imm = 16; - MONO_ADD_INS (cfg->cbb, ins); - - int esize = mono_class_value_size (mono_class_from_mono_type_internal (etype), NULL); - int store_opcode = mono_type_to_store_membase (cfg, etype); - for (int i = 0; i < fsig->param_count; ++i) - MONO_EMIT_NEW_STORE_MEMBASE (cfg, store_opcode, ins->dreg, i * esize, args [i]->dreg); - - load = emit_simd_ins (cfg, klass, OP_SSE_LOADU, ins->dreg, -1); - load->inst_c0 = 16; - load->inst_c1 = get_underlying_type (etype); - return load; - } - } - case SN_CreateScalarUnsafe: - return emit_simd_ins_for_sig (cfg, klass, OP_CREATE_SCALAR_UNSAFE, -1, arg0_type, fsig, args); - default: - break; - } - - return NULL; -} - static MonoInst* emit_vector128_t (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, MonoInst **args) { @@ -2029,8 +2040,6 @@ emit_amd64_intrinsics (const char *class_ns, const char *class_name, MonoCompile if (!strcmp (class_ns, "System.Runtime.Intrinsics")) { if (!strcmp (class_name, "Vector128`1")) return emit_vector128_t (cfg, cmethod, fsig, args); - if (!strcmp (class_name, "Vector128")) - return emit_vector128 (cfg, cmethod, fsig, args); if (!strcmp (class_name, "Vector256`1")) return emit_vector256_t (cfg, cmethod, fsig, args); } @@ -2097,6 +2106,13 @@ mono_emit_simd_intrinsics (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSign if (m_class_get_nested_in (cmethod->klass)) class_ns = m_class_get_name_space (m_class_get_nested_in (cmethod->klass)); +#if defined(TARGET_AMD64) || defined(TARGET_ARM64) + if (!strcmp (class_ns, "System.Runtime.Intrinsics")) { + if (!strcmp (class_name, "Vector128")) + return emit_vector128 (cfg, cmethod, fsig, args); + } +#endif // defined(TARGET_AMD64) || defined(TARGET_ARM64) + return emit_simd_intrinsics (class_ns, class_name, cfg, cmethod, fsig, args); } diff --git a/src/mono/mono/tests/Makefile.am b/src/mono/mono/tests/Makefile.am index 4cf22cabdd504..29bd9ec8d3bd6 100755 --- a/src/mono/mono/tests/Makefile.am +++ b/src/mono/mono/tests/Makefile.am @@ -1691,7 +1691,8 @@ CI_DISABLED_TESTS = \ main-returns-background-abort-resetabort.exe \ assemblyresolve_event3.exe \ finally_guard.exe \ - generic-xdomain.2.exe + generic-xdomain.2.exe \ + localloc-noinit.exe # failing tests which we temporarily disable for PRs # so they don't interfere with other people's work diff --git a/src/mono/mono/utils/mono-merp.c b/src/mono/mono/utils/mono-merp.c index 0da6f07a22f7a..7a21658d73487 100644 --- a/src/mono/mono/utils/mono-merp.c +++ b/src/mono/mono/utils/mono-merp.c @@ -129,7 +129,8 @@ typedef enum { MerpArchx86_64 = 1, MerpArchx86 = 2, MerpArchPPC = 3, - MerpArchPPC64 = 4 + MerpArchPPC64 = 4, + MerpArchARM64 = 5 } MerpArch; typedef enum @@ -218,6 +219,8 @@ get_merp_bitness (MerpArch arch) return "x64"; case MerpArchx86: return "x32"; + case MerpArchARM64: + return "arm64"; default: g_assert_not_reached (); } @@ -234,6 +237,8 @@ get_merp_arch (void) return MerpArchPPC; #elif defined(TARGET_POWERPC64) return MerpArchPPC64; +#elif defined(TARGET_ARM64) + return MerpArchARM64; #else g_assert_not_reached (); #endif diff --git a/src/mono/monoaotcross.proj b/src/mono/monoaotcross.proj index 99ad16a0655f7..64390f973183b 100644 --- a/src/mono/monoaotcross.proj +++ b/src/mono/monoaotcross.proj @@ -5,7 +5,16 @@ - Android-x64;Android-arm64;Android-x86;Android-arm + <_MonoCrossAOTTargetOS Condition="'$(MonoCrossAOTTargetOS)' != ''">+$(MonoCrossAOTTargetOS.ToLowerInvariant())+ + <_MonoGenerateOffsetsOSGroups Condition="'$(MonoGenerateOffsetsOSGroups)' != ''">+$(MonoGenerateOffsetsOSGroups.ToLowerInvariant())+ + <_MonoCrossAOTTargetOS Condition="$(_MonoGenerateOffsetsOSGroups.contains('+android+'))">$(_MonoCrossAOTTargetOS)+android+ + <_MonoCrossAOTTargetOS Condition="$(_MonoGenerateOffsetsOSGroups.contains('+browser+'))">$(_MonoCrossAOTTargetOS)+browser+ + <_MonoCrossAOTTargetOS Condition="$(_MonoGenerateOffsetsOSGroups.contains('+tvos+'))">$(_MonoCrossAOTTargetOS)+tvos+ + <_MonoCrossAOTTargetOS Condition="$(_MonoGenerateOffsetsOSGroups.contains('+ios+'))">$(_MonoCrossAOTTargetOS)+ios+ + $(MonoAotTargets);Android-x64;Android-arm64;Android-x86;Android-arm + $(MonoAotTargets);Browser-wasm + $(MonoAotTargets);tvOS-x64;tvOS-arm64 + $(MonoAotTargets);iOS-x64;iOS-arm64;iOS-x86;iOS-arm diff --git a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj index a48b961b142d2..123281934d2ce 100644 --- a/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj +++ b/src/mono/netcore/System.Private.CoreLib/System.Private.CoreLib.csproj @@ -50,30 +50,28 @@ x64 false - TARGET_64BIT;TARGET_AMD64;$(DefineConstants) + $(DefineConstants);TARGET_AMD64 x86 - TARGET_32BIT;$(DefineConstants) arm - TARGET_32BIT;TARGET_ARM;$(DefineConstants) + $(DefineConstants);TARGET_ARM AnyCPU - TARGET_64BIT;TARGET_ARM64;$(DefineConstants) + $(DefineConstants);TARGET_ARM64 AnyCPU - TARGET_32BIT;$(DefineConstants) false true - _LOGGING;DEBUG;$(DefineConstants) + $(DefineConstants);_LOGGING;DEBUG true @@ -103,7 +101,7 @@ $(NoWarn),618,67 - MONO_FEATURE_SRE;$(DefineConstants) + $(DefineConstants);MONO_FEATURE_SRE true true @@ -145,7 +143,6 @@ - diff --git a/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs b/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs deleted file mode 100644 index 30c03904489f3..0000000000000 --- a/src/mono/netcore/System.Private.CoreLib/src/Mono/MonoPInvokeCallbackAttribute.cs +++ /dev/null @@ -1,13 +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; - -namespace Mono -{ - [AttributeUsage(AttributeTargets.Method)] - internal sealed class MonoPInvokeCallbackAttribute : Attribute - { - public MonoPInvokeCallbackAttribute(Type type) {} - } -} diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Delegate.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Delegate.Mono.cs index a541fd398d731..21d5661aaff40 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Delegate.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Delegate.Mono.cs @@ -88,7 +88,7 @@ protected Delegate(object target, string method) }; } - protected Delegate([DynamicallyAccessedMembers(AllMethods)] Type target, string method) + protected Delegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method) { if (target is null) throw new ArgumentNullException(nameof(target)); @@ -183,7 +183,7 @@ public static Delegate CreateDelegate(Type type, object? firstArgument, MethodIn return CreateDelegate_internal(type, target, info, throwOnBindFailure); } - public static Delegate? CreateDelegate(Type type, [DynamicallyAccessedMembers(AllMethods)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) + public static Delegate? CreateDelegate(Type type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method, bool ignoreCase, bool throwOnBindFailure) { if (type is null) throw new ArgumentNullException(nameof(type)); @@ -214,7 +214,9 @@ public static Delegate CreateDelegate(Type type, object? firstArgument, MethodIn return CreateDelegate_internal(type, null, info, throwOnBindFailure); } - private static MethodInfo? GetCandidateMethod(RuntimeType type, [DynamicallyAccessedMembers(AllMethods)] Type target, string method, BindingFlags bflags, bool ignoreCase) + // GetCandidateMethod is annotated as DynamicallyAccessedMemberTypes.All because it will bind to non-public methods + // on a base type of methodType. Using All is currently the only way ILLinker will preserve these methods. + private static MethodInfo? GetCandidateMethod(RuntimeType type, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method, BindingFlags bflags, bool ignoreCase) { MethodInfo? invoke = GetDelegateInvokeMethod(type); if (invoke is null) diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/MulticastDelegate.cs b/src/mono/netcore/System.Private.CoreLib/src/System/MulticastDelegate.cs index 244e638253324..d36552cd50708 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/MulticastDelegate.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/MulticastDelegate.cs @@ -19,7 +19,7 @@ protected MulticastDelegate(object target, string method) { } - protected MulticastDelegate([DynamicallyAccessedMembers(AllMethods)] Type target, string method) + protected MulticastDelegate([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type target, string method) : base(target, method) { } diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Interlocked.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Interlocked.Mono.cs index 281854521f08c..47692e79a7df5 100644 --- a/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Interlocked.Mono.cs +++ b/src/mono/netcore/System.Private.CoreLib/src/System/Threading/Interlocked.Mono.cs @@ -80,9 +80,6 @@ public static partial class Interlocked [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern long CompareExchange(ref long location1, long value, long comparand); - [MethodImplAttribute(MethodImplOptions.InternalCall)] - public static extern IntPtr CompareExchange(ref IntPtr location1, IntPtr value, IntPtr comparand); - [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern double CompareExchange(ref double location1, double value, double comparand); @@ -114,10 +111,6 @@ public static T CompareExchange(ref T location1, T value, T comparand) where [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern long Exchange(ref long location1, long value); - [Intrinsic] - [MethodImplAttribute(MethodImplOptions.InternalCall)] - public static extern IntPtr Exchange(ref IntPtr location1, IntPtr value); - [Intrinsic] [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern double Exchange(ref double location1, double value); diff --git a/src/mono/netcore/sample/iOS/Program.csproj b/src/mono/netcore/sample/iOS/Program.csproj index 7806e60070050..f797af463084f 100644 --- a/src/mono/netcore/sample/iOS/Program.csproj +++ b/src/mono/netcore/sample/iOS/Program.csproj @@ -49,14 +49,14 @@ + LLVMPath="$(MicrosoftNetCoreAppRuntimePackDir)native\cross\$(RuntimeIdentifier)"> diff --git a/src/mono/netcore/sample/mbr/DeltaHelper.targets b/src/mono/netcore/sample/mbr/DeltaHelper.targets index 1d400bdcace5b..a3aa2f7ae0e03 100644 --- a/src/mono/netcore/sample/mbr/DeltaHelper.targets +++ b/src/mono/netcore/sample/mbr/DeltaHelper.targets @@ -1,24 +1,10 @@ - - - - - - <_JustDeltas Include="%(DeltaBase.Deltas)"/> - - - @(_JustDeltas->Count()) - - - - - - + + /Users/alklig/work/roslynildiff/roslynildiff -msbuild:$(MSBuildProjectFullPath) @@ -26,7 +12,7 @@ What other properties do I need to pass? Maybe roslynildiff should just expose an MSBuild task so we can pass everything --> $(RoslynILDiffArgs) -p:Configuration=$(Configuration) $(RoslynILDiffArgs) -p:RuntimeIdentifier=$(RuntimeIdentifier) - $(RoslynILDiffArgs) @(DeltaFiles, ' ') + $(RoslynILDiffArgs) -script:$(DeltaScript) @@ -74,6 +60,7 @@ DeltaOutputs = result; + diff --git a/src/mono/netcore/sample/mbr/browser/Makefile b/src/mono/netcore/sample/mbr/browser/Makefile index e4c115d70a786..fe4895cd5eda5 100644 --- a/src/mono/netcore/sample/mbr/browser/Makefile +++ b/src/mono/netcore/sample/mbr/browser/Makefile @@ -1,27 +1,12 @@ TOP=../../../../../.. -DOTNET=$(TOP)/dotnet.sh -ifeq ($(V),) -DOTNET_Q_ARGS=--nologo -v:q -consoleloggerparameters:NoSummary -else -DOTNET_Q_ARGS=--nologo -endif +include ../../wasm/wasm.mk -CONFIG?=Release +AOT= -all: build +PROJECT_NAME=WasmDelta.csproj -build: - $(DOTNET) build $(DOTNET_Q_ARGS) /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=$(CONFIG) WasmDelta.csproj +run: run-browser -clean: - rm -rf bin -run: - if ! $(DOTNET) tool list --global | grep dotnet-serve; then \ - echo "The tool dotnet-serve could not be found. Install with: $(DOTNET) tool install --global dotnet-serve"; \ - exit 1; \ - else \ - $(DOTNET) serve -d bin/$(CONFIG)/publish -p 8000; \ - fi diff --git a/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj b/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj index 7440308f883ed..92ee7ca3fbfd5 100644 --- a/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj +++ b/src/mono/netcore/sample/mbr/browser/WasmDelta.csproj @@ -1,65 +1,62 @@ - + + + + Release Exe bin - false - $(NetCoreAppToolCurrent) - wasm - Browser - $(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\$(Configuration)\runtimes\browser-wasm\ - $(MSBuildThisFileDirectory)obj\$(Configuration)\wasm - $(MSBuildThisFileDirectory)bin\$(Configuration)\publish + + $(MSBuildProjectDirectory)\bin\$(Configuration)\AppBundle\ + runtime.js + false + + + false + + PrepareDeltasForWasmApp;$(WasmBuildAppDependsOn) + + + + + false + true + false + false + false - - + - $(AppDir) - bin\WasmDelta.dll - runtime.js - - true + $(MSBuildProjectDirectory)\$(PublishDir)\ + $(WasmBuildDir)$(AssemblyName).dll - - - - - - - - - + - \%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension) + \%(_DeltaFileForPublish.Filename)%(_DeltaFileForPublish.Extension) - - - Always - - + + - - Program_v1.cs;Program_v2.cs - + - - - - - + + deltascript.json + 2 + + diff --git a/src/mono/netcore/sample/mbr/browser/deltascript.json b/src/mono/netcore/sample/mbr/browser/deltascript.json new file mode 100644 index 0000000000000..43ecf68676fb1 --- /dev/null +++ b/src/mono/netcore/sample/mbr/browser/deltascript.json @@ -0,0 +1,6 @@ +{ + "changes": [ + {"document": "Program.cs", "update": "Program_v1.cs"}, + {"document": "Program.cs", "update": "Program_v2.cs"} + ] +} diff --git a/src/mono/netcore/sample/mbr/browser/runtime.js b/src/mono/netcore/sample/mbr/browser/runtime.js index 7db3cb4fc70e7..aa866c0b5dd7c 100644 --- a/src/mono/netcore/sample/mbr/browser/runtime.js +++ b/src/mono/netcore/sample/mbr/browser/runtime.js @@ -6,6 +6,9 @@ var Module = { config.loaded_cb = function () { App.init (); }; + config.environment_variables = { + "MONO_METADATA_UPDATE": "1" + }; config.fetch_file_cb = function (asset) { return fetch (asset, { credentials: 'same-origin' }); } diff --git a/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj b/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj index d807eff508bdc..fb5886a37e0e6 100644 --- a/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj +++ b/src/mono/netcore/sample/mbr/console/ConsoleDelta.csproj @@ -16,11 +16,10 @@ - - - TestClass_v1.cs - - + + deltascript.json + 1 + diff --git a/src/mono/netcore/sample/mbr/console/deltascript.json b/src/mono/netcore/sample/mbr/console/deltascript.json new file mode 100644 index 0000000000000..75e1a2374cb2a --- /dev/null +++ b/src/mono/netcore/sample/mbr/console/deltascript.json @@ -0,0 +1,5 @@ +{ + "changes": [ + {"document": "TestClass.cs", "update": "TestClass_v1.cs"}, + ] +} diff --git a/src/mono/netcore/sample/wasm/Directory.Build.props b/src/mono/netcore/sample/wasm/Directory.Build.props index fa90e1e135602..a7035cc50319f 100644 --- a/src/mono/netcore/sample/wasm/Directory.Build.props +++ b/src/mono/netcore/sample/wasm/Directory.Build.props @@ -3,11 +3,6 @@ Release Exe - - false - true - link - false diff --git a/src/mono/netcore/sample/wasm/browser-profile/Makefile b/src/mono/netcore/sample/wasm/browser-profile/Makefile index a5094ae23cdcc..8c4bc7815dbc0 100644 --- a/src/mono/netcore/sample/wasm/browser-profile/Makefile +++ b/src/mono/netcore/sample/wasm/browser-profile/Makefile @@ -2,15 +2,14 @@ TOP=../../../../../.. include ../wasm.mk -build: - EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) /p:EnableProfiler=true /p:WasmBuildNative=true Wasm.BrowserProfile.Sample.csproj +PROJECT_NAME=Wasm.BrowserProfile.Sample.csproj +BUILD_ARGS=/p:WasmBuildNative=true /p:EnableProfiler=true +BUILD_PROFILED_ARGS=/p:RunAOTCompilation=true /p:AOTProfilePath=$(PROFILE_PATH) -run-aot-profiled: -ifeq ($(PROFILE_PATH),) - $(error PROFILE_PATH is not set) -endif - EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) /p:RunAOTCompilation=true /p:AOTProfilePath=$(PROFILE_PATH) /p:BuildAOTProfiled=true /p:Configuration=$(CONFIG) /p:TargetArchitecture=wasm /p:TargetOS=Browser $(MSBUILD_ARGS) Wasm.BrowserProfile.Sample.csproj +run: run-browser +get-aot-profile: override MSBUILD_ARGS+=$(BUILD_ARGS) get-aot-profile: build run -use-aot-profile: run-aot-profiled run +use-aot-profile: override MSBUILD_ARGS+=$(BUILD_PROFILED_ARGS) +use-aot-profile: build run diff --git a/src/mono/netcore/sample/wasm/browser-profile/README.md b/src/mono/netcore/sample/wasm/browser-profile/README.md new file mode 100644 index 0000000000000..6122e7b5c9ff5 --- /dev/null +++ b/src/mono/netcore/sample/wasm/browser-profile/README.md @@ -0,0 +1,69 @@ +## How to run a sample app with AOT profiling enabled + +### Setting up a project with profiling + +1. Define a `write_at` method. By default it is: + +``` +[MethodImpl(MethodImplOptions.NoInlining)] + public static void StopProfile(){} +``` + +2. Initialize the profiler in the main javascript (e.g. runtime.js) + +``` +var Module = { + onRuntimeInitialized: function () { + ... + + if (config.enable_profiler) + { + config.aot_profiler_options = { + write_at: "", + send_to: "System.Runtime.InteropServices.JavaScript.Runtime::DumpAotProfileData" + } + } +``` + +3. Call the `write_at` method at the end of the app, either in C# or in JS. To call the `write_at` method in JS, make use of bindings: + +`BINDING.call_static_method("<[ProjectName] Namespace.Class::StopProfile">, []);` + +When the `write_at` method is called, the `send_to` method `DumpAotProfileData` stores the profile data into `Module.aot_profile_data` + +4. Download `Module.aot_profile_data` in JS, using something similar to: + +``` +function saveProfile() { + var a = document.createElement('a'); + var blob = new Blob([Module.aot_profile_data]); + a.href = URL.createObjectURL(blob); + a.download = "data.aotprofile"; + // Append anchor to body. + document.body.appendChild(a); + a.click(); + + // Remove anchor from body + document.body.removeChild(a); +} +``` + +### Build and Run a project with profiling +1. To enable profiling during a build, we need to make use of WasmApp.InTree.targets/props by importing into the project file: + +``
+`` + +For more information on how to utilize WasmApp.InTree.targets/props consult the wasm build directory [README.md](../../../../wasm/build/README.md) + +2. To get the profile data, run: + +`make get-aot-profile` + +Which will build and run the current project with AOT disabled and the AOT profiler enabled. + +3. Go to localhost:8000 and the profile will automatically download. + +4. To use the profile data in the project, run: + +`make use-aot-profile PROFILE_PATH=` diff --git a/src/mono/netcore/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj b/src/mono/netcore/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj index b3662f2f697bf..9d14836ecb4f3 100644 --- a/src/mono/netcore/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj +++ b/src/mono/netcore/sample/wasm/browser-profile/Wasm.BrowserProfile.Sample.csproj @@ -2,9 +2,11 @@ true runtime.js + aot; + diff --git a/src/mono/netcore/sample/wasm/browser-profile/index.html b/src/mono/netcore/sample/wasm/browser-profile/index.html index 488e6c2c2c640..50eb0ff28c2ef 100644 --- a/src/mono/netcore/sample/wasm/browser-profile/index.html +++ b/src/mono/netcore/sample/wasm/browser-profile/index.html @@ -21,7 +21,7 @@ var test_exit = function(exit_code) { if (!is_testing) { - console.log(`test_exit: ${exitCode}`); + console.log(`test_exit: ${exit_code}`); return; } diff --git a/src/mono/netcore/sample/wasm/browser/Makefile b/src/mono/netcore/sample/wasm/browser/Makefile index 19edb33b9845e..3fe23fc375c0f 100644 --- a/src/mono/netcore/sample/wasm/browser/Makefile +++ b/src/mono/netcore/sample/wasm/browser/Makefile @@ -3,7 +3,9 @@ TOP=../../../../../.. include ../wasm.mk ifneq ($(AOT),) -DOTNET_PUBLISH_ARGS+=/p:RunAOTCompilation=true +override MSBUILD_ARGS+=/p:RunAOTCompilation=true endif -DOTNET_PUBLISH_ARGS+=Wasm.Browser.Sample.csproj +PROJECT_NAME=Wasm.Browser.Sample.csproj + +run: run-browser diff --git a/src/mono/netcore/sample/wasm/console/Makefile b/src/mono/netcore/sample/wasm/console/Makefile index a7362b583bb39..db854eb29ff5d 100644 --- a/src/mono/netcore/sample/wasm/console/Makefile +++ b/src/mono/netcore/sample/wasm/console/Makefile @@ -3,14 +3,13 @@ TOP=../../../../../.. include ../wasm.mk ifneq ($(AOT),) -DOTNET_PUBLISH_ARGS+=/p:RunAOTCompilation=true +override MSBUILD_ARGS+=/p:RunAOTCompilation=true endif ifneq ($(V),) DOTNET_MONO_LOG_LEVEL=--setenv=MONO_LOG_LEVEL=debug endif -DOTNET_PUBLISH_ARGS+=Wasm.Console.Sample.csproj +PROJECT_NAME=Wasm.Console.Sample.csproj -run: - cd bin/$(CONFIG)/AppBundle && ~/.jsvu/v8 --expose_wasm runtime.js -- $(DOTNET_MONO_LOG_LEVEL) --run Wasm.Console.Sample.dll +run: run-console diff --git a/src/mono/netcore/sample/wasm/wasm.mk b/src/mono/netcore/sample/wasm/wasm.mk index 11538ec3233ae..a6b27256b9308 100644 --- a/src/mono/netcore/sample/wasm/wasm.mk +++ b/src/mono/netcore/sample/wasm/wasm.mk @@ -13,15 +13,18 @@ WASM_DEFAULT_BUILD_ARGS?=/p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Confi all: build build: - EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(DOTNET_PUBLISH_ARGS) $(MSBUILD_ARGS) + EMSDK_PATH=$(realpath $(TOP)/src/mono/wasm/emsdk) $(DOTNET) publish $(DOTNET_Q_ARGS) $(WASM_DEFAULT_BUILD_ARGS) $(MSBUILD_ARGS) $(PROJECT_NAME) clean: - rm -rf bin + rm -rf bin $(TOP)/artifacts/obj/mono/$(PROJECT_NAME) -run: +run-browser: if ! $(DOTNET) tool list --global | grep dotnet-serve; then \ echo "The tool dotnet-serve could not be found. Install with: $(DOTNET) tool install --global dotnet-serve"; \ exit 1; \ else \ $(DOTNET) serve -d bin/$(CONFIG)/AppBundle -p 8000; \ fi + +run-console: + cd bin/$(CONFIG)/AppBundle && ~/.jsvu/v8 --expose_wasm runtime.js -- $(DOTNET_MONO_LOG_LEVEL) --run Wasm.Console.Sample.dll diff --git a/src/mono/wasm/Makefile b/src/mono/wasm/Makefile index 4f955afe57ff8..8b9ac50b08085 100644 --- a/src/mono/wasm/Makefile +++ b/src/mono/wasm/Makefile @@ -6,6 +6,7 @@ escape_quote = $(subst ",\",$(1)) DOTNET=$(TOP)/dotnet.sh JSVU=$(HOME)/.jsvu CHROMEDRIVER?=$(HOME)/.chromedriver +GECKODRIVER?=$(HOME)/.geckodriver # # These variables are set by wasm.proj @@ -22,6 +23,7 @@ SYSTEM_NATIVE_LIBDIR?=$(TOP)/src/libraries/Native/Unix/System.Native ENABLE_ES6?=false _MSBUILD_WASM_BUILD_ARGS=/p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=$(CONFIG) ENABLE_METADATA_UPDATE?=false +XHARNESS_BROWSER?=chrome all: build-native icu-files source-files header-files @@ -180,7 +182,7 @@ run-tests-%: PATH="$(JSVU):$(PATH)" $(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) run-browser-tests-%: - PATH="$(CHROMEDRIVER):$(PATH)" XHARNESS_COMMAND=test-browser $(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) + PATH="$(GECKODRIVER):$(CHROMEDRIVER):$(PATH)" XHARNESS_COMMAND="test-browser --browser=$(XHARNESS_BROWSER)" $(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) run-debugger-tests: if [ ! -z "$(TEST_FILTER)" ]; then \ diff --git a/src/mono/wasm/build/WasmApp.InTree.props b/src/mono/wasm/build/WasmApp.InTree.props index bd11ae4ec86fc..f9540f1fac7a2 100644 --- a/src/mono/wasm/build/WasmApp.InTree.props +++ b/src/mono/wasm/build/WasmApp.InTree.props @@ -8,5 +8,10 @@ $(NetCoreAppToolCurrent) $(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\$(Configuration)\runtimes\browser-wasm\ $(MonoProjectRoot)wasm\emsdk + + false + true + link + false diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets index 35e84f8ed378a..18afa169ce1c1 100644 --- a/src/mono/wasm/build/WasmApp.targets +++ b/src/mono/wasm/build/WasmApp.targets @@ -6,14 +6,42 @@ @@ -32,15 +60,15 @@ - + <_AotInputAssemblies Include="@(_WasmAssemblies->Distinct())"> @(MonoAOTCompilerDefaultAotArguments, ';') @(MonoAOTCompilerDefaultProcessArguments, ';') - + <_WasmAssemblies Remove="@(_WasmAssemblies)" /> - AotInterp + AotInterp LLVMOnly @@ -48,17 +76,17 @@ CompilerBinaryPath="$(MicrosoftNetCoreAppRuntimePackRidDir)native\cross\$(PackageRID)\mono-aot-cross" Mode="$(AOTMode)" OutputType="AsmOnly" - Assemblies="@(AotInputAssemblies)" + Assemblies="@(_AotInputAssemblies)" UseAotDataFile="false" - AotProfilePath="$(AOTProfilePath)" - Profilers="aot;" + AOTProfilePath="$(AOTProfilePath)" + Profilers="$(WasmProfilers)" AotModulesTablePath="$(WasmBuildDir)driver-gen.c" UseLLVM="true" DisableParallelAot="true" LLVMPath="$(EMSDK_PATH)\upstream\bin"> + - @@ -92,7 +120,6 @@ AppDir="$(WasmAppDir)" MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackRidDir)" MainJS="$(WasmMainJSPath)" - EnableProfiler="$(EnableProfiler)" Assemblies="@(_WasmAssemblies)" InvariantGlobalization="$(WasmInvariantGlobalization)" SatelliteAssemblies="@(WasmSatelliteAssemblies)" @@ -100,6 +127,7 @@ IcuDataFileName="$(WasmIcuDataFileName)" RemoteSources="@(WasmRemoteSources)" ExtraFilesToDeploy="@(WasmExtraFilesToDeploy)" + ExtraConfig="@(WasmExtraConfig)" DebugLevel="$(WasmDebugLevel)"> @@ -117,20 +145,20 @@ - + $(MicrosoftNetCoreAppRuntimePackRidDir)native\src\emcc-flags.txt $(MicrosoftNetCoreAppRuntimePackRidDir)native\src\emcc-version.txt $(WasmBuildDir)emcc-version.txt - + + $(_DefaultEmccFlags) $(EmccFlags) $(EmccFlags) -s DISABLE_EXCEPTION_CATCHING=0 $(EmccFlags) -DENABLE_AOT=1 -DDRIVER_GEN=1 - $(EmccFlags) -DDRIVER_GEN=1 <_EmsdkEnvSourceCommand>source $(EMSDK_PATH)/emsdk_env.sh > /dev/null 2>&1 <_EmccCommand>$(_EmsdkEnvSourceCommand) && emcc @@ -138,23 +166,14 @@ + - - - -void mono_profiler_init_aot (const char *desc)%3B -EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_profiler_init_aot (desc)%3B } - - - - + + <_WasmPInvokeModules Include="libSystem.Native" /> <_WasmPInvokeModules Include="libSystem.IO.Compression.Native" /> <_WasmPInvokeModules Include="libSystem.Globalization.Native" /> @@ -195,6 +214,25 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_ + + + $(EmccFlags) -DDRIVER_GEN=1 + +void mono_profiler_init_aot (const char *desc)%3B +EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_aot (const char *desc) { mono_profiler_init_aot (desc)%3B } + + + <_DriverGenCPath>$(WasmBuildDir)driver-gen.c + + + + + + + + + + $(WasmAppDir)run-v8.sh diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs index 6e5e4a948dd41..dfe99aed5a769 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ArrayTests.cs @@ -202,77 +202,68 @@ async Task TestSimpleArrayLocals(int line, int col, string entry_method_name, st string local_var_name_prefix, object[] array, object[] array_elem_props, bool test_prev_frame = false, int frame_idx = 0, bool use_cfo = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}', { (test_prev_frame ? "true" : "false") }" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - Assert.Equal(4, locals.Count()); - CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0); - CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0); - CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); - CheckBool(locals, "call_other", test_prev_frame); + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Equal(4, locals.Count()); + CheckArray(locals, $"{local_var_name_prefix}_arr", $"{etype_name}[]", array?.Length ?? 0); + CheckArray(locals, $"{local_var_name_prefix}_arr_empty", $"{etype_name}[]", 0); + CheckObject(locals, $"{local_var_name_prefix}_arr_null", $"{etype_name}[]", is_null: true); + CheckBool(locals, "call_other", test_prev_frame); - var local_arr_name = $"{local_var_name_prefix}_arr"; + var local_arr_name = $"{local_var_name_prefix}_arr"; - JToken prefix_arr; - if (use_cfo) - { // Use `Runtime.callFunctionOn` to get the properties - var frame = pause_location["callFrames"][frame_idx]; - var name = local_arr_name; - var fl = await GetProperties(frame["callFrameId"].Value()); - var l_obj = GetAndAssertObjectWithName(locals, name); - var l_objectId = l_obj["value"]["objectId"]?.Value(); + JToken prefix_arr; + if (use_cfo) + { // Use `Runtime.callFunctionOn` to get the properties + var frame = pause_location["callFrames"][frame_idx]; + var name = local_arr_name; + var fl = await GetProperties(frame["callFrameId"].Value()); + var l_obj = GetAndAssertObjectWithName(locals, name); + var l_objectId = l_obj["value"]["objectId"]?.Value(); - Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}"); + Assert.True(!String.IsNullOrEmpty(l_objectId), $"No objectId found for {name}"); - prefix_arr = await GetObjectWithCFO(l_objectId); - } - else - { - prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name); - } + prefix_arr = await GetObjectWithCFO(l_objectId); + } + else + { + prefix_arr = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], local_arr_name); + } - await CheckProps(prefix_arr, array, local_arr_name); + await CheckProps(prefix_arr, array, local_arr_name); - if (array_elem_props?.Length > 0) + if (array_elem_props?.Length > 0) + { + for (int i = 0; i < array_elem_props.Length; i++) { - for (int i = 0; i < array_elem_props.Length; i++) + var i_str = i.ToString(); + var label = $"{local_var_name_prefix}_arr[{i}]"; + if (array_elem_props[i] == null) { - var i_str = i.ToString(); - var label = $"{local_var_name_prefix}_arr[{i}]"; - if (array_elem_props[i] == null) - { - var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value() == i_str); - Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); + var act_i = prefix_arr.FirstOrDefault(jt => jt["name"]?.Value() == i_str); + Assert.True(act_i != null, $"[{label}] Couldn't find array element [{i_str}]"); - await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label); - } - else - { - await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label); - } + await CheckValue(act_i["value"], TObject(etype_name, is_null: true), label); + } + else + { + await CompareObjectPropertiesFor(prefix_arr, i_str, array_elem_props[i], label: label); } } + } - var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); - await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty"); - }); + var props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], $"{local_var_name_prefix}_arr_empty"); + await CheckProps(props, new object[0], "${local_var_name_prefix}_arr_empty"); async Task GetObjectWithCFO(string objectId, JObject fn_args = null) { @@ -287,7 +278,7 @@ async Task GetObjectWithCFO(string objectId, JObject fn_args = null) cfo_args["arguments"] = fn_args; // callFunctionOn - var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + var result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); return await GetProperties(result.Value["result"]["objectId"]?.Value(), fn_args); } @@ -298,79 +289,71 @@ async Task GetObjectWithCFO(string objectId, JObject fn_args = null) [InlineData(true)] public async Task InspectObjectArrayMembers(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 227; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ObjectArrayMembers"; string method_name = "PlaceholderMethod"; int frame_idx = 1; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - Assert.Single(locals); - CheckObject(locals, "c", "DebuggerTests.Container"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + Assert.Single(locals); + CheckObject(locals, "c", "DebuggerTests.Container"); - var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c"); - await CheckProps(c_props, new - { - id = TString("c#id"), - ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3), - ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3), - PointsProperty = TArray("DebuggerTests.Point[]", 2), - PointsField = TArray("DebuggerTests.Point[]", 2) - }, - "c" - ); + var c_props = await GetObjectOnFrame(pause_location["callFrames"][frame_idx], "c"); + await CheckProps(c_props, new + { + id = TString("c#id"), + ClassArrayProperty = TArray("DebuggerTests.SimpleClass[]", 3), + ClassArrayField = TArray("DebuggerTests.SimpleClass[]", 3), + PointsProperty = TArray("DebuggerTests.Point[]", 2), + PointsField = TArray("DebuggerTests.Point[]", 2) + }, + "c" + ); - await CompareObjectPropertiesFor(c_props, "ClassArrayProperty", - new[] - { + await CompareObjectPropertiesFor(c_props, "ClassArrayProperty", + new[] + { TSimpleClass(5, -2, "ClassArrayProperty#Id#0", "Green"), TSimpleClass(30, 1293, "ClassArrayProperty#Id#1", "Green"), TObject("DebuggerTests.SimpleClass", is_null : true) - }, - label: "InspectLocalsWithStructsStaticAsync"); + }, + label: "InspectLocalsWithStructsStaticAsync"); - await CompareObjectPropertiesFor(c_props, "ClassArrayField", - new[] - { + await CompareObjectPropertiesFor(c_props, "ClassArrayField", + new[] + { TObject("DebuggerTests.SimpleClass", is_null : true), TSimpleClass(5, -2, "ClassArrayField#Id#1", "Blue"), TSimpleClass(30, 1293, "ClassArrayField#Id#2", "Green") - }, - label: "c#ClassArrayField"); + }, + label: "c#ClassArrayField"); - await CompareObjectPropertiesFor(c_props, "PointsProperty", - new[] - { + await CompareObjectPropertiesFor(c_props, "PointsProperty", + new[] + { TPoint(5, -2, "PointsProperty#Id#0", "Green"), TPoint(123, 0, "PointsProperty#Id#1", "Blue"), - }, - label: "c#PointsProperty"); + }, + label: "c#PointsProperty"); - await CompareObjectPropertiesFor(c_props, "PointsField", - new[] - { + await CompareObjectPropertiesFor(c_props, "PointsField", + new[] + { TPoint(5, -2, "PointsField#Id#0", "Green"), TPoint(123, 0, "PointsField#Id#1", "Blue"), - }, - label: "c#PointsField"); - }); + }, + label: "c#PointsField"); } [Theory] @@ -378,53 +361,46 @@ await CompareObjectPropertiesFor(c_props, "PointsField", [InlineData(true)] public async Task InspectValueTypeArrayLocalsStaticAsync(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 157; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; string method_name = "MoveNext"; // BUG: this should be ValueTypeLocalsAsync int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', false" // *false* here keeps us only in the static method - + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" // *false* here keeps us only in the static method + + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new - { - call_other = TBool(false), - gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), - gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), - gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), - gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), - // BUG: this shouldn't be null! - points = TObject("DebuggerTests.Point[]", is_null: true) - }, "ValueTypeLocalsAsync#locals"); - - var local_var_name_prefix = "gvclass"; - await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new - { - Id = TString(null), - Color = TEnum("DebuggerTests.RGB", "Red"), - Value = TPoint(0, 0, null, "Red") - }); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, method_name); + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + gvclass_arr = TArray("DebuggerTests.SimpleGenericStruct[]", 2), + gvclass_arr_empty = TArray("DebuggerTests.SimpleGenericStruct[]"), + gvclass_arr_null = TObject("DebuggerTests.SimpleGenericStruct[]", is_null: true), + gvclass = TValueType("DebuggerTests.SimpleGenericStruct"), + // BUG: this shouldn't be null! + points = TObject("DebuggerTests.Point[]", is_null: true) + }, "ValueTypeLocalsAsync#locals"); + + var local_var_name_prefix = "gvclass"; + await CompareObjectPropertiesFor(frame_locals, local_var_name_prefix, new + { + Id = TString(null), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(0, 0, null, "Red") + }); - await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", - new[] - { + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", + new[] + { new { Id = TString("gvclass_arr#1#Id"), @@ -437,11 +413,10 @@ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr", Color = TEnum("DebuggerTests.RGB", "Blue"), Value = TPoint(10, 20, "gvclass_arr#2#Value#Id", "Green") } - } - ); - await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty", - new object[0]); - }); + } + ); + await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_empty", + new object[0]); } // TODO: Check previous frame too @@ -450,58 +425,51 @@ await CompareObjectPropertiesFor(frame_locals, $"{local_var_name_prefix}_arr_emp [InlineData(true)] public async Task InspectValueTypeArrayLocalsInstanceAsync(bool use_cfo) { - var insp = new Inspector(); //Collect events - var scripts = SubscribeToScripts(insp); int line = 170; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:ValueTypeLocalsAsync"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', true" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + t1 = TObject("DebuggerTests.SimpleGenericStruct"), + @this = TObject("DebuggerTests.ArrayTestsClass"), + point_arr = TArray("DebuggerTests.Point[]", 2), + point = TValueType("DebuggerTests.Point") + }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); + + await CompareObjectPropertiesFor(frame_locals, "t1", + new { - t1 = TObject("DebuggerTests.SimpleGenericStruct"), - @this = TObject("DebuggerTests.ArrayTestsClass"), - point_arr = TArray("DebuggerTests.Point[]", 2), - point = TValueType("DebuggerTests.Point") - }, "InspectValueTypeArrayLocalsInstanceAsync#locals"); - - await CompareObjectPropertiesFor(frame_locals, "t1", - new - { - Id = TString("gvclass_arr#1#Id"), - Color = TEnum("DebuggerTests.RGB", "Red"), - Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") - }); + Id = TString("gvclass_arr#1#Id"), + Color = TEnum("DebuggerTests.RGB", "Red"), + Value = TPoint(100, 200, "gvclass_arr#1#Value#Id", "Red") + }); - await CompareObjectPropertiesFor(frame_locals, "point_arr", - new[] - { + await CompareObjectPropertiesFor(frame_locals, "point_arr", + new[] + { TPoint(5, -2, "point_arr#Id#0", "Red"), TPoint(123, 0, "point_arr#Id#1", "Blue"), - } - ); + } + ); - await CompareObjectPropertiesFor(frame_locals, "point", - TPoint(45, 51, "point#Id", "Green")); - }); + await CompareObjectPropertiesFor(frame_locals, "point", + TPoint(45, 51, "point#Id", "Green")); } [Theory] @@ -509,39 +477,31 @@ await CompareObjectPropertiesFor(frame_locals, "point", [InlineData(true)] public async Task InspectValueTypeArrayLocalsInAsyncStaticStructMethod(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 244; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); - //await SetBreakpoint (debugger_test_loc, 143, 3); + await SetBreakpoint(debugger_test_loc, line, col); + //await SetBreakpoint (debugger_test_loc, 143, 3); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', false" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', false" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new - { - call_other = TBool(false), - local_i = TNumber(5), - sc = TSimpleClass(10, 45, "sc#Id", "Blue") - }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); - }); + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(false), + local_i = TNumber(5), + sc = TSimpleClass(10, 45, "sc#Id", "Blue") + }, "InspectValueTypeArrayLocalsInAsyncStaticStructMethod#locals"); } [Theory] @@ -549,56 +509,48 @@ await insp.Ready(async (cli, token) => [InlineData(true)] public async Task InspectValueTypeArrayLocalsInAsyncInstanceStructMethod(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); int line = 251; int col = 12; string entry_method_name = "[debugger-test] DebuggerTests.ArrayTestsClass:EntryPointForStructMethod"; int frame_idx = 0; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-array-test.cs"; - await SetBreakpoint(debugger_test_loc, line, col); + await SetBreakpoint(debugger_test_loc, line, col); - var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + - $"'{entry_method_name}', true" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method_async (" + + $"'{entry_method_name}', true" + + "); }, 1);"; - // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + // BUG: Should be InspectValueTypeArrayLocalsInstanceAsync + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, "MoveNext"); + + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + sc_arg = TObject("DebuggerTests.SimpleClass"), + @this = TValueType("DebuggerTests.Point"), + local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + }, + "locals#0"); - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + await CompareObjectPropertiesFor(frame_locals, "local_gs", + new { - sc_arg = TObject("DebuggerTests.SimpleClass"), - @this = TValueType("DebuggerTests.Point"), - local_gs = TValueType("DebuggerTests.SimpleGenericStruct") + Id = TString("local_gs#Id"), + Color = TEnum("DebuggerTests.RGB", "Green"), + Value = TNumber(4) }, - "locals#0"); + label: "local_gs#0"); - await CompareObjectPropertiesFor(frame_locals, "local_gs", - new - { - Id = TString("local_gs#Id"), - Color = TEnum("DebuggerTests.RGB", "Green"), - Value = TNumber(4) - }, - label: "local_gs#0"); - - await CompareObjectPropertiesFor(frame_locals, "sc_arg", - TSimpleClass(10, 45, "sc_arg#Id", "Blue"), - label: "sc_arg#0"); - - await CompareObjectPropertiesFor(frame_locals, "this", - TPoint(90, -4, "point#Id", "Green"), - label: "this#0"); - }); + await CompareObjectPropertiesFor(frame_locals, "sc_arg", + TSimpleClass(10, 45, "sc_arg#Id", "Blue"), + label: "sc_arg#0"); + + await CompareObjectPropertiesFor(frame_locals, "this", + TPoint(90, -4, "point#Id", "Green"), + label: "this#0"); } [Fact] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs index 663da5f1c50a6..6d83a777253e4 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BadHarnessInitTests.cs @@ -14,14 +14,7 @@ namespace DebuggerTests { public class BadHarnessInitTests : DebuggerTestBase { - internal Inspector insp; - protected Dictionary scripts; - - public BadHarnessInitTests(string driver = "debugger-driver.html") : base(driver) - { - insp = new Inspector(); - scripts = SubscribeToScripts(insp); - } + public override async Task InitializeAsync() => await Task.CompletedTask; [Fact] public async Task InvalidInitCommands() diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs index 09094cea2212f..3b320494538dd 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/CallFunctionOnTests.cs @@ -30,12 +30,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array { var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else @@ -43,12 +43,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn0, "big", bp_loc, line, col, res_array // Check for a __proto__ object // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); await CheckProps(obj_own.Value["result"], new { @@ -90,12 +90,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col, var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); // isOwn = false, accessorPropertiesOnly = true - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else @@ -104,12 +104,12 @@ await RunCallFunctionOn(eval_fn, vscode_fn1, "big", bp_loc, line, col, // Ignoring the __proto__ property // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; var num_elems_recd = len == 0 ? 0 : num_elems_fetch; @@ -142,24 +142,24 @@ await RunCallFunctionOn(eval_fn, var is_js = bp_loc.EndsWith(".js", StringComparison.Ordinal); // getProperties (isOwn = false, accessorPropertiesOnly = true) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); else AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); // getProperties (isOwn = true, accessorPropertiesOnly = false) - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); await CheckProps(obj_own.Value["result"], new { @@ -188,12 +188,12 @@ await RunCallFunctionOn(eval_fn, var is_js = bp_loc.EndsWith(".js"); // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); if (is_js) await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); @@ -202,12 +202,12 @@ await RunCallFunctionOn(eval_fn, // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); // AssertEqual (2, obj_own.Value ["result"].Count (), $"{label}-obj_own.count"); @@ -239,23 +239,23 @@ public async Task RunOnVTArray(bool roundtrip) => await RunCallFunctionOn( var ret_len = 5; // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); AssertEqual(0, obj_accessors.Value["result"]?.Count(), "obj_accessors-count"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; await CheckProps(obj_own_val, new @@ -299,22 +299,22 @@ public async Task RunOnCFOValueTypeResult(bool roundtrip) => await RunCallFuncti { // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); AssertEqual(0, obj_accessors.Value["result"].Count(), "obj_accessors-count"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; var dt = new DateTime(2020, 1, 2, 3, 4, 5); @@ -345,23 +345,23 @@ public async Task RunOnJSObject(bool roundtrip) => await RunCallFunctionOn( { // getProperties (own=false) - var obj_accessors = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_accessors = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = true, ownProperties = false - }), ctx.token); + }), token); await CheckProps(obj_accessors.Value["result"], new { __proto__ = TIgnore() }, "obj_accessors"); // getProperties (own=true) // isOwn = true, accessorPropertiesOnly = false - var obj_own = await ctx.cli.SendCommand("Runtime.getProperties", JObject.FromObject(new + var obj_own = await cli.SendCommand("Runtime.getProperties", JObject.FromObject(new { objectId = result.Value["result"]["objectId"].Value(), accessorPropertiesOnly = false, ownProperties = true - }), ctx.token); + }), token); var obj_own_val = obj_own.Value["result"]; await CheckProps(obj_own_val, new @@ -442,74 +442,65 @@ public async Task RunOnArrayReturnArrayByValue(string eval_fn, string bp_loc, in [InlineData("invoke_static_method ('[debugger-test] DebuggerTests.CallFunctionOnTest:LocalsTest', 10);", "dotnet://debugger-test.dll/debugger-cfo-test.cs", 23, 12, true)] public async Task RunOnArrayReturnPrimitive(string eval_fn, string bp_loc, int line, int col, bool return_by_val) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetBreakpoint(bp_loc, line, col); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); - - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, scripts, pause_location ["callFrames"][0]["location"]); - // Check the object at the bp - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, "big"); - var obj_id = obj["value"]["objectId"].Value(); + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var obj_id = obj["value"]["objectId"].Value(); - var cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 5; }", - objectId = obj_id - }); + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 5; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], TNumber(5), "cfo-res"); + // callFunctionOn + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], TNumber(5), "cfo-res"); - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 'test value'; }", - objectId = obj_id - }); + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 'test value'; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res"); + // callFunctionOn + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], JObject.FromObject(new { type = "string", value = "test value" }), "cfo-res"); - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return null; }", - objectId = obj_id - }); + cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return null; }", + objectId = obj_id + }); - // value of @returnByValue doesn't matter, as the returned value - // is a primitive - if (return_by_val) - cfo_args["returnByValue"] = return_by_val; + // value of @returnByValue doesn't matter, as the returned value + // is a primitive + if (return_by_val) + cfo_args["returnByValue"] = return_by_val; - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res"); - }); + // callFunctionOn + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckValue(result.Value["result"], JObject.Parse("{ type: 'object', subtype: 'null', value: null }"), "cfo-res"); } public static TheoryData SilentErrorsTestData(bool? silent) => new TheoryData @@ -523,45 +514,36 @@ await insp.Ready(async (cli, token) => [MemberData(nameof(SilentErrorsTestData), true)] public async Task CFOWithSilentReturnsErrors(string eval_fn, string bp_loc, int line, int col, bool? silent) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + await SetBreakpoint(bp_loc, line, col); - // callFunctionOn - var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = "window.setTimeout(function() { " + eval_fn + " }, 1);"; + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, "big"); - var big_obj_id = obj["value"]["objectId"].Value(); - var error_msg = "#This is an error message#"; + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, "big"); + var big_obj_id = obj["value"]["objectId"].Value(); + var error_msg = "#This is an error message#"; - // Check the object at the bp - var cfo_args = JObject.FromObject(new - { - functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", - objectId = big_obj_id - }); + // Check the object at the bp + var cfo_args = JObject.FromObject(new + { + functionDeclaration = $"function () {{ throw Error ('{error_msg}'); }}", + objectId = big_obj_id + }); - if (silent.HasValue) - cfo_args["silent"] = silent; + if (silent.HasValue) + cfo_args["silent"] = silent; - // callFunctionOn, Silent does not change the result, except that the error - // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - Assert.False(result.IsOk, "result.IsOk"); - Assert.True(result.IsErr, "result.IsErr"); + // callFunctionOn, Silent does not change the result, except that the error + // doesn't get reported, and the execution is NOT paused even with setPauseOnException=true + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + Assert.False(result.IsOk, "result.IsOk"); + Assert.True(result.IsErr, "result.IsErr"); - var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value()?.Contains(error_msg); - Assert.True((hasErrorMessage ?? false), "Exception message not found"); - }); + var hasErrorMessage = result.Error["exceptionDetails"]?["exception"]?["description"]?.Value()?.Contains(error_msg); + Assert.True((hasErrorMessage ?? false), "Exception message not found"); } public static TheoryData, string, bool> GettersTestData(string local_name, bool use_cfo) => new TheoryData, string, bool> @@ -771,7 +753,7 @@ await RunCallFunctionOn( async Task GetPropertiesAndCheckAccessors(JObject get_prop_req, int num_fields) { - var res = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + var res = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); if (!res.IsOk) Assert.True(false, $"Runtime.getProperties failed for {get_prop_req.ToString()}, with Result: {res}"); @@ -808,7 +790,7 @@ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int objectId = ptd_id + "_invalid" }); - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + var res = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); Assert.True(res.IsErr); }); @@ -816,35 +798,26 @@ public async Task RunOnInvalidCfoId(string eval_fn, string bp_loc, int line, int [MemberData(nameof(NegativeTestsData), false)] public async Task RunOnInvalidThirdSegmentOfObjectId(string eval_fn, string bp_loc, int line, int col, bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - await SetBreakpoint(bp_loc, line, col); + UseCallFunctionOnBeforeGetProperties = use_cfo; + await SetBreakpoint(bp_loc, line, col); - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); - var ptd_id = ptd["value"]["objectId"].Value(); - - var cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return 0; }", - objectId = ptd_id + "_invalid" - }); + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - Assert.True(res.IsErr); + var cfo_args = JObject.FromObject(new + { + functionDeclaration = "function () { return 0; }", + objectId = ptd_id + "_invalid" }); + + var res = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + Assert.True(res.IsErr); } [Theory] @@ -852,33 +825,24 @@ await insp.Ready(async (cli, token) => [MemberData(nameof(NegativeTestsData), true)] public async Task InvalidPropertyGetters(string eval_fn, string bp_loc, int line, int col, bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + await SetBreakpoint(bp_loc, line, col); + UseCallFunctionOnBeforeGetProperties = use_cfo; - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr })); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + await SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr })); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); - var ptd_id = ptd["value"]["objectId"].Value(); + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var ptd = GetAndAssertObjectWithName(frame_locals, "ptd"); + var ptd_id = ptd["value"]["objectId"].Value(); - var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 }; - foreach (var invalid_arg in invalid_args) - { - var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg); - AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}"); - } - }); + var invalid_args = new object[] { "NonExistant", String.Empty, null, 12310 }; + foreach (var invalid_arg in invalid_args) + { + var getter_res = await InvokeGetter(JObject.FromObject(new { value = new { objectId = ptd_id } }), invalid_arg); + AssertEqual("undefined", getter_res.Value["result"]?["type"]?.ToString(), $"Expected to get undefined result for non-existant accessor - {invalid_arg}"); + } } [Theory] @@ -922,81 +886,72 @@ public async Task ReturnNullFromCFO(string eval_fn, string bp_loc, int line, int async Task RunCallFunctionOn(string eval_fn, string fn_decl, string local_name, string bp_loc, int line, int col, int res_array_len = -1, Func test_fn = null, bool returnByValue = false, JArray fn_args = null, bool roundtrip = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetBreakpoint(bp_loc, line, col); + + // callFunctionOn + var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); + + // Um for js we get "scriptId": "6" + // CheckLocation (bp_loc, line, col, scripts, pause_location ["callFrames"][0]["location"]); + + // Check the object at the bp + var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); + var obj = GetAndAssertObjectWithName(frame_locals, local_name); + var obj_id = obj["value"]["objectId"].Value(); - await Ready(); - await insp.Ready(async (cli, token) => + var cfo_args = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + functionDeclaration = fn_decl, + objectId = obj_id + }); - // callFunctionOn - var eval_expr = $"window.setTimeout(function() {{ {eval_fn} }}, 1);"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + if (fn_args != null) + cfo_args["arguments"] = fn_args; - // Um for js we get "scriptId": "6" - // CheckLocation (bp_loc, line, col, ctx.scripts, pause_location ["callFrames"][0]["location"]); + if (returnByValue) + cfo_args["returnByValue"] = returnByValue; - // Check the object at the bp - var frame_locals = await GetProperties(pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value()); - var obj = GetAndAssertObjectWithName(frame_locals, local_name); - var obj_id = obj["value"]["objectId"].Value(); + // callFunctionOn + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); + await CheckCFOResult(result); - var cfo_args = JObject.FromObject(new + // If it wasn't `returnByValue`, then try to run a new function + // on that *returned* object + // This second function, just returns the object as-is, so the same + // test_fn is re-usable. + if (!returnByValue && roundtrip) + { + cfo_args = JObject.FromObject(new { - functionDeclaration = fn_decl, - objectId = obj_id + functionDeclaration = "function () { return this; }", + objectId = result.Value["result"]["objectId"]?.Value() }); if (fn_args != null) cfo_args["arguments"] = fn_args; - if (returnByValue) - cfo_args["returnByValue"] = returnByValue; + result = await cli.SendCommand("Runtime.callFunctionOn", cfo_args, token); - // callFunctionOn - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); await CheckCFOResult(result); + } - // If it wasn't `returnByValue`, then try to run a new function - // on that *returned* object - // This second function, just returns the object as-is, so the same - // test_fn is re-usable. - if (!returnByValue && roundtrip) - { - cfo_args = JObject.FromObject(new - { - functionDeclaration = "function () { return this; }", - objectId = result.Value["result"]["objectId"]?.Value() - }); - - if (fn_args != null) - cfo_args["arguments"] = fn_args; - - result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); - - await CheckCFOResult(result); - } - - if (test_fn != null) - await test_fn(result); + if (test_fn != null) + await test_fn(result); - return; + return; - async Task CheckCFOResult(Result result) - { - if (returnByValue) - return; + async Task CheckCFOResult(Result result) + { + if (returnByValue) + return; - if (res_array_len < 0) - await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res"); - else - await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res"); - } - }); + if (res_array_len < 0) + await CheckValue(result.Value["result"], TObject("Object"), $"cfo-res"); + else + await CheckValue(result.Value["result"], TArray("Array", res_array_len), $"cfo-res"); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs index 40a156c4f3182..2d3707fec1d8d 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DateTimeTests.cs @@ -18,39 +18,30 @@ public class DateTimeList : DebuggerTestBase [InlineData("de-DE", "dddd, d. MMMM yyyy HH:mm:ss", "dddd, d. MMMM yyyy", "HH:mm:ss", "dd.MM.yyyy", "HH:mm")] public async Task CheckDateTimeLocale(string locale, string fdtp, string ldp, string ltp, string sdp, string stp) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-datetime-test.cs"; + await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); - await SetBreakpointInMethod("debugger-test", "DebuggerTests.DateTimeTest", "LocaleTest", 15); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + + $"'{locale}'); }}, 1);", + debugger_test_loc, 25, 12, "LocaleTest", + locals_fn: async (locals) => + { + DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; + CultureInfo.CurrentCulture = new CultureInfo(locale, false); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.DateTimeTest:LocaleTest'," + - $"'{locale}'); }}, 1);", - debugger_test_loc, 25, 12, "LocaleTest", - locals_fn: async (locals) => + await CheckProps(locals, new { - DateTimeFormatInfo dtfi = CultureInfo.GetCultureInfo(locale).DateTimeFormat; - CultureInfo.CurrentCulture = new CultureInfo(locale, false); - - await CheckProps(locals, new - { - fdtp = TString(fdtp), - ldp = TString(ldp), - ltp = TString(ltp), - sdp = TString(sdp), - stp = TString(stp), - dt = TDateTime(new DateTime(2020, 1, 2, 3, 4, 5)) - }, "locals", num_fields: 8); - } - ); - - }); + fdtp = TString(fdtp), + ldp = TString(ldp), + ltp = TString(ltp), + sdp = TString(sdp), + stp = TString(stp), + dt = TDateTime(new DateTime(2020, 1, 2, 3, 4, 5)) + }, "locals", num_fields: 8); + } + ); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index c238433e50265..1e9f155b19f16 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -20,10 +20,16 @@ namespace DebuggerTests { - public class DebuggerTestBase + public class DebuggerTestBase : IAsyncLifetime { + internal InspectorClient cli; + internal Inspector insp; + protected CancellationToken token; + protected Dictionary scripts; protected Task startTask; + public bool UseCallFunctionOnBeforeGetProperties; + static string s_debuggerTestAppPath; protected static string DebuggerTestAppPath { @@ -74,12 +80,37 @@ static string FindChromePath() public DebuggerTestBase(string driver = "debugger-driver.html") { + insp = new Inspector(); + cli = insp.Client; + scripts = SubscribeToScripts(insp); + startTask = TestHarnessProxy.Start(FindChromePath(), DebuggerTestAppPath, driver); } + public virtual async Task InitializeAsync() + { + Func)>> fn = (client, token) => + { + Func)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token)); + var init_cmds = new List<(string, Task)> + { + getInitCmdFn("Profiler.enable"), + getInitCmdFn("Runtime.enable"), + getInitCmdFn("Debugger.enable"), + getInitCmdFn("Runtime.runIfWaitingForDebugger") + }; + + return init_cmds; + }; + + await Ready(); + await insp.OpenSessionAsync(fn); + } + + public virtual async Task DisposeAsync() => await insp.ShutdownAsync().ConfigureAwait(false); + public Task Ready() => startTask; - internal DebugTestContext ctx; internal Dictionary dicScriptsIdToUrl; internal Dictionary dicFileToUrl; internal Dictionary SubscribeToScripts(Inspector insp) @@ -110,88 +141,70 @@ internal Dictionary SubscribeToScripts(Inspector insp) internal async Task CheckInspectLocalsAtBreakpointSite(string url_key, int line, int column, string function_name, string eval_expression, Action test_fn = null, Func wait_for_event_fn = null, bool use_cfo = false) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - - var bp = await SetBreakpoint(url_key, line, column); + var bp = await SetBreakpoint(url_key, line, column); - await EvaluateAndCheck( - eval_expression, url_key, line, column, - function_name, - wait_for_event_fn: async (pause_location) => - { + await EvaluateAndCheck( + eval_expression, url_key, line, column, + function_name, + wait_for_event_fn: async (pause_location) => + { //make sure we're on the right bp Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location!["callFrames"]?[0]; - var scope = top_frame["scopeChain"][0]; - if (wait_for_event_fn != null) - await wait_for_event_fn(pause_location); - else - await Task.CompletedTask; - }, - locals_fn: (locals) => - { - if (test_fn != null) - test_fn(locals); - } - ); - }); + var scope = top_frame!["scopeChain"]?[0]; + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); + else + await Task.CompletedTask; + }, + locals_fn: (locals) => + { + if (test_fn != null) + test_fn(locals); + } + ); } // sets breakpoint by method name and line offset internal async Task CheckInspectLocalsAtBreakpointSite(string type, string method, int line_offset, string bp_function_name, string eval_expression, Action locals_fn = null, Func wait_for_event_fn = null, bool use_cfo = false, string assembly = "debugger-test.dll", int col = 0) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; - var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col); + var bp = await SetBreakpointInMethod(assembly, type, method, line_offset, col); - var args = JObject.FromObject(new { expression = eval_expression }); - var res = await ctx.cli.SendCommand("Runtime.evaluate", args, ctx.token); - if (!res.IsOk) - { - Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); - Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); - } + var args = JObject.FromObject(new { expression = eval_expression }); + var res = await cli.SendCommand("Runtime.evaluate", args, token); + if (!res.IsOk) + { + Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); + Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); + } - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - if (bp_function_name != null) - Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value()); + if (bp_function_name != null) + Assert.Equal(bp_function_name, pause_location["callFrames"]?[0]?["functionName"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location!["callFrames"]?[0]; - var scope = top_frame["scopeChain"][0]; + var scope = top_frame?["scopeChain"]?[0]; - if (wait_for_event_fn != null) - await wait_for_event_fn(pause_location); + if (wait_for_event_fn != null) + await wait_for_event_fn(pause_location); - if (locals_fn != null) - { - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - locals_fn(locals); - } - }); + if (locals_fn != null) + { + var locals = await GetProperties(pause_location?["callFrames"]?[0]?["callFrameId"]?.Value()); + locals_fn(locals); + } } internal void CheckLocation(string script_loc, int line, int column, Dictionary scripts, JToken location) @@ -255,13 +268,13 @@ internal async Task CheckDateTime(JToken value, DateTime expected, string label await CheckDateTimeValue(value, expected, label); } - internal async Task CheckDateTime(JToken locals, string name, DateTime expected, string label="") + internal async Task CheckDateTime(JToken locals, string name, DateTime expected, string label = "") { var obj = GetAndAssertObjectWithName(locals, name, label); await CheckDateTimeValue(obj["value"], expected, label); } - internal async Task CheckDateTimeValue(JToken value, DateTime expected, string label="") + internal async Task CheckDateTimeValue(JToken value, DateTime expected, string label = "") { await CheckDateTimeMembers(value, expected, label); @@ -270,7 +283,7 @@ internal async Task CheckDateTimeValue(JToken value, DateTime expected, string l // FIXME: check some float properties too - async Task CheckDateTimeMembers(JToken v, DateTime exp_dt, string label="") + async Task CheckDateTimeMembers(JToken v, DateTime exp_dt, string label = "") { AssertEqual("System.DateTime", v["className"]?.Value(), $"{label}#className"); AssertEqual(exp_dt.ToString(), v["description"]?.Value(), $"{label}#description"); @@ -319,7 +332,7 @@ internal void CheckArray(JToken locals, string name, string class_name, int leng GetAndAssertObjectWithName(locals, name)["value"], TArray(class_name, length), name).Wait(); - internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label="") + internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label = "") { var l = obj.FirstOrDefault(jt => jt["name"]?.Value() == name); if (l == null) @@ -329,7 +342,7 @@ internal JToken GetAndAssertObjectWithName(JToken obj, string name, string label internal async Task SendCommand(string method, JObject args) { - var res = await ctx.cli.SendCommand(method, args, ctx.token); + var res = await cli.SendCommand(method, args, token); if (!res.IsOk) { Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); @@ -355,7 +368,7 @@ internal async Task RunUntil(string methodName) await SetBreakpointInMethod("debugger-test", "DebuggerTest", methodName); // This will run all the tests until it hits the bp await Evaluate("window.setTimeout(function() { invoke_run_all (); }, 1);"); - var wait_res = await ctx.insp.WaitFor(Inspector.PAUSE); + var wait_res = await insp.WaitFor(Inspector.PAUSE); AssertLocation(wait_res, "locals_inner"); return wait_res; } @@ -371,7 +384,7 @@ internal async Task InvokeGetter(JToken obj, object arguments, string fn if (returnByValue != null) req["returnByValue"] = returnByValue.Value; - var res = await ctx.cli.SendCommand("Runtime.callFunctionOn", req, ctx.token); + var res = await cli.SendCommand("Runtime.callFunctionOn", req, token); Assert.True(expect_ok == res.IsOk, $"InvokeGetter failed for {req} with {res}"); return res; @@ -403,23 +416,23 @@ internal async Task EvaluateAndCheck(string expression, string script_l internal async Task SendCommandAndCheck(JObject args, string method, string script_loc, int line, int column, string function_name, Func wait_for_event_fn = null, Action locals_fn = null, string waitForEvent = Inspector.PAUSE) { - var res = await ctx.cli.SendCommand(method, args, ctx.token); + var res = await cli.SendCommand(method, args, token); if (!res.IsOk) { Console.WriteLine($"Failed to run command {method} with args: {args?.ToString()}\nresult: {res.Error.ToString()}"); Assert.True(false, $"SendCommand for {method} failed with {res.Error.ToString()}"); } - var wait_res = await ctx.insp.WaitFor(waitForEvent); + var wait_res = await insp.WaitFor(waitForEvent); JToken top_frame = wait_res["callFrames"]?[0]; if (function_name != null) { AssertEqual(function_name, wait_res["callFrames"]?[0]?["functionName"]?.Value(), top_frame?.ToString()); } - Console.WriteLine (top_frame); + Console.WriteLine(top_frame); if (script_loc != null && line >= 0) - CheckLocation(script_loc, line, column, ctx.scripts, top_frame["location"]); + CheckLocation(script_loc, line, column, scripts, top_frame["location"]); if (wait_for_event_fn != null) await wait_for_event_fn(wait_res); @@ -659,14 +672,14 @@ internal async Task CheckValue(JToken actual_val, JToken exp_val, string label) } catch { - Console.WriteLine ($"Expected: {exp_val}. Actual: {actual_val}"); + Console.WriteLine($"Expected: {exp_val}. Actual: {actual_val}"); throw; } } internal async Task GetLocalsForFrame(JToken frame, string script_loc, int line, int column, string function_name) { - CheckLocation(script_loc, line, column, ctx.scripts, frame["location"]); + CheckLocation(script_loc, line, column, scripts, frame["location"]); Assert.Equal(function_name, frame["functionName"].Value()); return await GetProperties(frame["callFrameId"].Value()); @@ -708,7 +721,7 @@ internal async Task GetObjectOnLocals(JToken locals, string name) /* @fn_args is for use with `Runtime.callFunctionOn` only */ internal async Task GetProperties(string id, JToken fn_args = null, bool? own_properties = null, bool? accessors_only = null, bool expect_ok = true) { - if (ctx.UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) + if (UseCallFunctionOnBeforeGetProperties && !id.StartsWith("dotnet:scope:")) { var fn_decl = "function () { return this; }"; var cfo_args = JObject.FromObject(new @@ -719,7 +732,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool if (fn_args != null) cfo_args["arguments"] = fn_args; - var result = await ctx.cli.SendCommand("Runtime.callFunctionOn", cfo_args, ctx.token); + 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; @@ -739,7 +752,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool get_prop_req["accessorPropertiesOnly"] = accessors_only.Value; } - var frame_props = await ctx.cli.SendCommand("Runtime.getProperties", get_prop_req, ctx.token); + 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; @@ -770,7 +783,7 @@ internal async Task GetProperties(string id, JToken fn_args = null, bool expression = expression }); - var res = await ctx.cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, ctx.token); + var res = await cli.SendCommand("Debugger.evaluateOnCallFrame", evaluate_req, token); AssertEqual(expect_ok, res.IsOk, $"Debugger.evaluateOnCallFrame ('{expression}', scope: {id}) returned {res.IsOk} instead of {expect_ok}, with Result: {res}"); if (res.IsOk) return (res.Value["result"], res); @@ -785,7 +798,7 @@ internal async Task RemoveBreakpoint(string id, bool expect_ok = true) breakpointId = id }); - var res = await ctx.cli.SendCommand("Debugger.removeBreakpoint", remove_bp, ctx.token); + var res = await cli.SendCommand("Debugger.removeBreakpoint", remove_bp, token); Assert.True(expect_ok ? res.IsOk : res.IsErr); return res; @@ -797,7 +810,7 @@ internal async Task SetBreakpoint(string url_key, int line, int column, JObject.FromObject(new { lineNumber = line, columnNumber = column, url = dicFileToUrl[url_key], }) : JObject.FromObject(new { lineNumber = line, columnNumber = column, urlRegex = url_key, }); - var bp1_res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); Assert.True(expect_ok ? bp1_res.IsOk : bp1_res.IsErr); return bp1_res; @@ -805,7 +818,7 @@ internal async Task SetBreakpoint(string url_key, int line, int column, internal async Task SetPauseOnException(string state) { - var exc_res = await ctx.cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), ctx.token); + var exc_res = await cli.SendCommand("Debugger.setPauseOnExceptions", JObject.FromObject(new { state = state }), token); return exc_res; } @@ -814,7 +827,7 @@ internal async Task SetBreakpointInMethod(string assembly, string type, var req = JObject.FromObject(new { assemblyName = assembly, typeName = type, methodName = method, lineOffset = lineOffset }); // Protocol extension - var res = await ctx.cli.SendCommand("DotnetDebugger.getMethodLocation", req, ctx.token); + var res = await cli.SendCommand("DotnetDebugger.getMethodLocation", req, token); Assert.True(res.IsOk); var m_url = res.Value["result"]["url"].Value(); @@ -827,7 +840,7 @@ internal async Task SetBreakpointInMethod(string assembly, string type, url = m_url }); - res = await ctx.cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, ctx.token); + res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); Assert.True(res.IsOk); return res; @@ -917,24 +930,6 @@ internal static JObject TDateTime(DateTime dt) => JObject.FromObject(new }); } - class DebugTestContext - { - public InspectorClient cli; - public Inspector insp; - public CancellationToken token; - public Dictionary scripts; - - public bool UseCallFunctionOnBeforeGetProperties; - - public DebugTestContext(InspectorClient cli, Inspector insp, CancellationToken token, Dictionary scripts) - { - this.cli = cli; - this.insp = insp; - this.token = token; - this.scripts = scripts; - } - } - class DotnetObjectId { public string Scheme { get; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs index 2218b9a794511..6462e86505d33 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests.cs @@ -49,26 +49,26 @@ public async Task EvaluateTypeInstanceMembers(string prefix, int bias, string ty var dateTime = new DateTime(2010, 9, 8, 7, 6, 5 + bias); var DTProp = dateTime.AddMinutes(10); - foreach (var pad in new[] { String.Empty, " " }) - { - var padded_prefix = pad + prefix; - await EvaluateOnCallFrameAndCheck(id, - ($"{padded_prefix}a", TNumber(4)), - - // fields - ($"{padded_prefix}dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())), - ($"{padded_prefix}dateTime", TDateTime(dateTime)), - ($"{padded_prefix}dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), - - // properties - ($"{padded_prefix}DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), - ($"{padded_prefix}DTProp", TDateTime(DTProp)), - ($"{padded_prefix}DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())), - - ($"{padded_prefix}IntProp", TNumber(9)), - ($"{padded_prefix}NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true)) - ); - } + foreach (var pad in new[] { String.Empty, " " }) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + ($"{padded_prefix}a", TNumber(4)), + + // fields + ($"{padded_prefix}dateTime.TimeOfDay", TValueType("System.TimeSpan", dateTime.TimeOfDay.ToString())), + ($"{padded_prefix}dateTime", TDateTime(dateTime)), + ($"{padded_prefix}dateTime.TimeOfDay.Minutes", TNumber(dateTime.TimeOfDay.Minutes)), + + // properties + ($"{padded_prefix}DTProp.TimeOfDay.Minutes", TNumber(DTProp.TimeOfDay.Minutes)), + ($"{padded_prefix}DTProp", TDateTime(DTProp)), + ($"{padded_prefix}DTProp.TimeOfDay", TValueType("System.TimeSpan", DTProp.TimeOfDay.ToString())), + + ($"{padded_prefix}IntProp", TNumber(9)), + ($"{padded_prefix}NullIfAIsNotZero", TObject("DebuggerTests.EvaluateTestsClassWithProperties", is_null: true)) + ); + } }); [Theory] @@ -145,13 +145,13 @@ public async Task EvaluateLocalsAsync() => await CheckInspectLocalsAtBreakpointS { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - // sc_arg - { + // sc_arg + { var (sc_arg, _) = await EvaluateOnCallFrame(id, "sc_arg"); await CheckValue(sc_arg, TObject("DebuggerTests.SimpleClass"), nameof(sc_arg)); - // Check that we did get the correct object - var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); + // Check that we did get the correct object + var sc_arg_props = await GetProperties(sc_arg["objectId"]?.Value()); await CheckProps(sc_arg_props, new { X = TNumber(10), @@ -167,8 +167,8 @@ await EvaluateOnCallFrameAndCheck(id, ("sc_arg.Id + (sc_arg.X==10 ? \"_is_ten\" : \"_not_ten\")", TString($"sc#Id_is_ten"))); } - // local_gs - { + // local_gs + { var (local_gs, _) = await EvaluateOnCallFrame(id, "local_gs"); await CheckValue(local_gs, TValueType("DebuggerTests.SimpleGenericStruct"), nameof(local_gs)); @@ -221,28 +221,28 @@ public async Task InheritedAndPrivateMembersInAClass(string prefix) { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - foreach(var pad in new [] { String.Empty, " "}) - { - var padded_prefix = pad + prefix; - await EvaluateOnCallFrameAndCheck(id, - // overridden - ($"{padded_prefix}FirstName + \"_foo\"", TString("DerivedClass#FirstName_foo")), - ($"{padded_prefix}DateTimeForOverride.Date.Year", TNumber(2190)), - ($"{padded_prefix}DateTimeForOverride.Date.Year - 10", TNumber(2180)), - ($"\"foo_\" + {padded_prefix}StringPropertyForOverrideWithAutoProperty", TString("foo_DerivedClass#StringPropertyForOverrideWithAutoProperty")), - - // private - ($"{padded_prefix}_stringField + \"_foo\"", TString("DerivedClass#_stringField_foo")), - ($"{padded_prefix}_stringField", TString("DerivedClass#_stringField")), - ($"{padded_prefix}_dateTime.Second + 4", TNumber(7)), - ($"{padded_prefix}_DTProp.Second + 4", TNumber(13)), - - // inherited public - ($"\"foo_\" + {padded_prefix}Base_AutoStringProperty", TString("foo_base#Base_AutoStringProperty")), - // inherited private - ($"{padded_prefix}_base_dateTime.Date.Year - 10", TNumber(2124)) - ); - } + foreach (var pad in new[] { String.Empty, " " }) + { + var padded_prefix = pad + prefix; + await EvaluateOnCallFrameAndCheck(id, + // overridden + ($"{padded_prefix}FirstName + \"_foo\"", TString("DerivedClass#FirstName_foo")), + ($"{padded_prefix}DateTimeForOverride.Date.Year", TNumber(2190)), + ($"{padded_prefix}DateTimeForOverride.Date.Year - 10", TNumber(2180)), + ($"\"foo_\" + {padded_prefix}StringPropertyForOverrideWithAutoProperty", TString("foo_DerivedClass#StringPropertyForOverrideWithAutoProperty")), + + // private + ($"{padded_prefix}_stringField + \"_foo\"", TString("DerivedClass#_stringField_foo")), + ($"{padded_prefix}_stringField", TString("DerivedClass#_stringField")), + ($"{padded_prefix}_dateTime.Second + 4", TNumber(7)), + ($"{padded_prefix}_DTProp.Second + 4", TNumber(13)), + + // inherited public + ($"\"foo_\" + {padded_prefix}Base_AutoStringProperty", TString("foo_base#Base_AutoStringProperty")), + // inherited private + ($"{padded_prefix}_base_dateTime.Date.Year - 10", TNumber(2124)) + ); + } }); [Fact] @@ -322,8 +322,8 @@ public async Task EvaluateOnPreviousFrames(string type_name, bool is_valuetype) var dt_local = new DateTime(2020, 3, 4, 5, 6, 7); var dt_this = new DateTime(2010, 9, 8, 7, 6, 5); - // At EvaluateShadow - { + // At EvaluateShadow + { var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id0, ("dateTime", TDateTime(dt_local)), @@ -339,38 +339,38 @@ await EvaluateOnCallFrameFail(id1, ("dateTime", "ReferenceError"), ("this.dateTime", "ReferenceError")); - // obj available only on the -1 frame - await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7))); + // obj available only on the -1 frame + await EvaluateOnCallFrameAndCheck(id1, ("obj.IntProp", TNumber(7))); } await SetBreakpointInMethod("debugger-test.dll", type_name, "SomeMethod", 1); pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "SomeMethod"); - // At SomeMethod + // At SomeMethod - // TODO: change types also.. so, that `this` is different! + // TODO: change types also.. so, that `this` is different! - // Check frame0 - { + // Check frame0 + { var id0 = pause_location["callFrames"][0]["callFrameId"].Value(); - // 'me' and 'dateTime' are reversed in this method - await EvaluateOnCallFrameAndCheck(id0, - ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)), - ("this.dateTime", TDateTime(dt_this)), - ("me", TDateTime(dt_local)), + // 'me' and 'dateTime' are reversed in this method + await EvaluateOnCallFrameAndCheck(id0, + ("dateTime", is_valuetype ? TValueType(type_name) : TObject(type_name)), + ("this.dateTime", TDateTime(dt_this)), + ("me", TDateTime(dt_local)), - // local variable shadows field, but isn't "live" yet - ("DTProp", TString(null)), + // local variable shadows field, but isn't "live" yet + ("DTProp", TString(null)), - // access field via `this.` - ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); + // access field via `this.` + ("this.DTProp", TDateTime(dt_this.AddMinutes(10)))); await EvaluateOnCallFrameFail(id0, ("obj", "ReferenceError")); } - // check frame1 - { + // check frame1 + { var id1 = pause_location["callFrames"][1]["callFrameId"].Value(); await EvaluateOnCallFrameAndCheck(id1, @@ -388,15 +388,15 @@ await EvaluateOnCallFrameAndCheck(id1, await EvaluateOnCallFrameFail(id1, ("obj", "ReferenceError")); } - // check frame2 - { + // check frame2 + { var id2 = pause_location["callFrames"][2]["callFrameId"].Value(); - // Only obj should be available - await EvaluateOnCallFrameFail(id2, - ("dateTime", "ReferenceError"), - ("this.dateTime", "ReferenceError"), - ("me", "ReferenceError")); + // Only obj should be available + await EvaluateOnCallFrameFail(id2, + ("dateTime", "ReferenceError"), + ("this.dateTime", "ReferenceError"), + ("me", "ReferenceError")); await EvaluateOnCallFrameAndCheck(id2, ("obj", is_valuetype ? TValueType(type_name) : TObject(type_name))); } @@ -405,32 +405,23 @@ await EvaluateOnCallFrameFail(id2, [Fact] public async Task JSEvaluate() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - var bp_loc = "/other.js"; var line = 76; var col = 1; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await SetBreakpoint(bp_loc, line, col); + await SetBreakpoint(bp_loc, line, col); - var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)"; - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var eval_expr = "window.setTimeout(function() { eval_call_on_frame_test (); }, 1)"; + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var id = pause_location["callFrames"][0]["callFrameId"].Value(); + var id = pause_location["callFrames"][0]["callFrameId"].Value(); - await EvaluateOnCallFrameFail(id, - ("me.foo", null), - ("obj.foo.bar", null)); + await EvaluateOnCallFrameFail(id, + ("me.foo", null), + ("obj.foo.bar", null)); - await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true); - }); + await EvaluateOnCallFrame(id, "obj.foo", expect_ok: true); } [Fact] @@ -441,23 +432,23 @@ public async Task NegativeTestsInInstanceMethod() => await CheckInspectLocalsAtB { var id = pause_location["callFrames"][0]["callFrameId"].Value(); - // Use '.' on a primitive member - await EvaluateOnCallFrameFail(id, - //BUG: TODO: - //("a)", "CompilationError"), + // Use '.' on a primitive member + await EvaluateOnCallFrameFail(id, + //BUG: TODO: + //("a)", "CompilationError"), - ("this.a.", "ReferenceError"), - ("a.", "ReferenceError"), + ("this.a.", "ReferenceError"), + ("a.", "ReferenceError"), - ("this..a", "CompilationError"), - (".a.", "ReferenceError"), + ("this..a", "CompilationError"), + (".a.", "ReferenceError"), - ("me.foo", "ReferenceError"), + ("me.foo", "ReferenceError"), - ("this.a + non_existant", "ReferenceError"), + ("this.a + non_existant", "ReferenceError"), - ("this.NullIfAIsNotZero.foo", "ReferenceError"), - ("NullIfAIsNotZero.foo", "ReferenceError")); + ("this.NullIfAIsNotZero.foo", "ReferenceError"), + ("NullIfAIsNotZero.foo", "ReferenceError")); }); [Fact] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs index 6f339c28ea90f..1be0b18f325b5 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/ExceptionTests.cs @@ -16,102 +16,83 @@ public class ExceptionTests : DebuggerTestBase [Fact] public async Task ExceptionTestAll() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-exception-test.cs"; - - await SetPauseOnException("all"); + await SetPauseOnException("all"); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null); - //stop in the managed caught exception - pause_location = await WaitForManagedException(pause_location); + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, null); + //stop in the managed caught exception + pause_location = await WaitForManagedException(pause_location); - AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause0"); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause0"); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "DebuggerTests.CustomException", - uncaught = false - }), "exception0.data"); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = false + }), "exception0.data"); - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "not implemented caught"); + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented caught"); - pause_location = await WaitForManagedException(null); - AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause1"); + pause_location = await WaitForManagedException(null); + AssertEqual("run", pause_location["callFrames"]?[0]?["functionName"]?.Value(), "pause1"); - //stop in the uncaught exception - CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]); + //stop in the uncaught exception + CheckLocation(debugger_test_loc, 28, 16, scripts, pause_location["callFrames"][0]["location"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "DebuggerTests.CustomException", - uncaught = true - }), "exception1.data"); - - exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "not implemented uncaught"); - }); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "DebuggerTests.CustomException", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "not implemented uncaught"); } [Fact] public async Task JSExceptionTestAll() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + await SetPauseOnException("all"); - await SetPauseOnException("all"); + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null); - var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; - var pause_location = await EvaluateAndCheck(eval_expr, null, 0, 0, "exception_caught_test", null, null); - - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "TypeError", - uncaught = false - }), "exception0.data"); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "TypeError", + uncaught = false + }), "exception0.data"); - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "exception caught"); + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception caught"); - pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test"); + pause_location = await SendCommandAndCheck(null, "Debugger.resume", null, 0, 0, "exception_uncaught_test"); - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "RangeError", - uncaught = true - }), "exception1.data"); - - exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", "exception uncaught"); - }); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = "RangeError", + uncaught = true + }), "exception1.data"); + + exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", "exception uncaught"); } // FIXME? BUG? We seem to get the stack trace for Runtime.exceptionThrown at `call_method`, @@ -119,88 +100,69 @@ await CheckValue(pause_location["data"], JObject.FromObject(new [Fact] public async Task ExceptionTestNone() { - var insp = new Inspector(); //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = "[debugger-test] DebuggerTests.ExceptionTestsClass:TestExceptions"; + await SetPauseOnException("none"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + var eval_expr = "window.setTimeout(function() { invoke_static_method (" + + $"'{entry_method_name}'" + + "); }, 1);"; - await SetPauseOnException("none"); + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + var eo = JObject.Parse(ae.Message); - var eval_expr = "window.setTimeout(function() { invoke_static_method (" + - $"'{entry_method_name}'" + - "); }, 1);"; + // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value (), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - try - { - await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); - } - catch (ArgumentException ae) + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new { - var eo = JObject.Parse(ae.Message); - - // AssertEqual (line, eo ["exceptionDetails"]?["lineNumber"]?.Value (), "lineNumber"); - AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - - await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "Error" // BUG?: "DebuggerTests.CustomException" - }), "exception"); + type = "object", + subtype = "error", + className = "Error" // BUG?: "DebuggerTests.CustomException" + }), "exception"); - return; - } + return; + } - Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); - }); + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); } [Fact] public async Task JSExceptionTestNone() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + await SetPauseOnException("none"); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; - await SetPauseOnException("none"); + int line = 44; + try + { + await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); + } + catch (ArgumentException ae) + { + Console.WriteLine($"{ae}"); + var eo = JObject.Parse(ae.Message); - var eval_expr = "window.setTimeout(function () { exceptions_test (); }, 1)"; + AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value(), "lineNumber"); + AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - int line = 44; - try - { - await EvaluateAndCheck(eval_expr, null, 0, 0, "", null, null); - } - catch (ArgumentException ae) + await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new { - Console.WriteLine($"{ae}"); - var eo = JObject.Parse(ae.Message); - - AssertEqual(line, eo["exceptionDetails"]?["lineNumber"]?.Value(), "lineNumber"); - AssertEqual("Uncaught", eo["exceptionDetails"]?["text"]?.Value(), "text"); - - await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = "RangeError" - }), "exception"); + type = "object", + subtype = "error", + className = "RangeError" + }), "exception"); - return; - } + return; + } - Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); - }); + Assert.True(false, "Expected to get an ArgumentException from the uncaught user exception"); } [Theory] @@ -211,31 +173,22 @@ await CheckValue(eo["exceptionDetails"]?["exception"], JObject.FromObject(new public async Task ExceptionTestUncaught(string eval_fn, string loc, int line, int col, string fn_name, string exception_type, string exception_message) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetPauseOnException("uncaught"); + await SetPauseOnException("uncaught"); - var eval_expr = $"window.setTimeout({eval_fn}, 1);"; - var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name); + var eval_expr = $"window.setTimeout({eval_fn}, 1);"; + var pause_location = await EvaluateAndCheck(eval_expr, loc, line, col, fn_name); - Assert.Equal("exception", pause_location["reason"]); - await CheckValue(pause_location["data"], JObject.FromObject(new - { - type = "object", - subtype = "error", - className = exception_type, - uncaught = true - }), "exception.data"); - - var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); - CheckString(exception_members, "message", exception_message); - }); + Assert.Equal("exception", pause_location["reason"]); + await CheckValue(pause_location["data"], JObject.FromObject(new + { + type = "object", + subtype = "error", + className = exception_type, + uncaught = true + }), "exception.data"); + + var exception_members = await GetProperties(pause_location["data"]["objectId"]?.Value()); + CheckString(exception_members, "message", exception_message); } async Task WaitForManagedException(JObject pause_location) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs index 2f35e8cfdc0e0..3215ef1184d89 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/GetPropertiesTests.cs @@ -188,7 +188,7 @@ public async Task InspectTypeInheritedMembers(string type_name, bool? own_proper }); public static IEnumerable MembersForLocalNestedStructData(bool is_async) - => StructGetPropertiesTestData(false).Select (datum => datum [1..]); + => StructGetPropertiesTestData(false).Select(datum => datum[1..]); [Theory] [MemberData(nameof(MembersForLocalNestedStructData), parameters: false)] @@ -217,7 +217,7 @@ public async Task MembersForLocalNestedStruct(bool? own_properties, bool? access AssertEqual(expected_names.Length, cs_props.Count(), $"expected number of properties"); }); - public static TheoryData JSGetPropertiesTestData(bool test_js)=> new TheoryData + public static TheoryData JSGetPropertiesTestData(bool test_js) => new TheoryData { // default, no args set { @@ -282,49 +282,40 @@ public async Task MembersForLocalNestedStruct(bool? own_properties, bool? access // [MemberData(nameof(JSGetPropertiesTestData), parameters: false)] public async Task GetPropertiesTestJSAndManaged(bool test_js, bool? own_properties, bool? accessors_only, string[] expected_names) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - - await Ready(); - await insp.Ready(async (cli, token) => + string eval_expr; + if (test_js) { - ctx = new DebugTestContext(cli, insp, token, scripts); - string eval_expr; - if (test_js) - { - await SetBreakpoint("/other.js", 93, 1); - eval_expr = "window.setTimeout(function() { get_properties_test (); }, 1)"; - } - else - { - await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.GetPropertiesTests.DerivedClassForJSTest", "run", 2); - eval_expr = "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClassForJSTest:run'); }, 1)"; - } + await SetBreakpoint("/other.js", 93, 1); + eval_expr = "window.setTimeout(function() { get_properties_test (); }, 1)"; + } + else + { + await SetBreakpointInMethod("debugger-test.dll", "DebuggerTests.GetPropertiesTests.DerivedClassForJSTest", "run", 2); + eval_expr = "window.setTimeout(function() { invoke_static_method ('[debugger-test] DebuggerTests.GetPropertiesTests.DerivedClassForJSTest:run'); }, 1)"; + } - var result = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), ctx.token); - var pause_location = await ctx.insp.WaitFor(Inspector.PAUSE); + var result = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = eval_expr }), token); + var pause_location = await insp.WaitFor(Inspector.PAUSE); - var id = pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(); + var id = pause_location["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(); - var frame_locals = await GetProperties(id); - var obj = GetAndAssertObjectWithName(frame_locals, "obj"); - var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value(), - own_properties: own_properties, accessors_only: accessors_only); + var frame_locals = await GetProperties(id); + var obj = GetAndAssertObjectWithName(frame_locals, "obj"); + var obj_props = await GetProperties(obj["value"]?["objectId"]?.Value(), + own_properties: own_properties, accessors_only: accessors_only); - IEnumerable filtered_props; - if (test_js) - { - filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value() == true); - } - else - { - // we don't set `enumerable` right now - filtered_props = obj_props.Children().Where(jt=> true); - } + IEnumerable filtered_props; + if (test_js) + { + filtered_props = obj_props.Children().Where(jt => jt["enumerable"]?.Value() == true); + } + else + { + // we don't set `enumerable` right now + filtered_props = obj_props.Children().Where(jt => true); + } - var expected_props = new Dictionary () + var expected_props = new Dictionary() { // own {"owner_name", (TString("foo"), true)}, @@ -336,13 +327,12 @@ await insp.Ready(async (cli, token) => {"available", (TGetter("available"), false)}, }; - await CheckExpectedProperties( - expected_names, - name => filtered_props.Where(jt => jt["name"]?.Value () == name).SingleOrDefault(), - expected_props); + await CheckExpectedProperties( + expected_names, + name => filtered_props.Where(jt => jt["name"]?.Value() == name).SingleOrDefault(), + expected_props); - AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties"); - }); + AssertEqual(expected_names.Length, filtered_props.Count(), $"expected number of properties"); } private async Task CheckExpectedProperties(string[] expected_names, Func get_actual_prop, Dictionary all_props) @@ -356,7 +346,7 @@ private async Task CheckExpectedProperties(string[] expected_names, Func () == true, $"{exp_name}#isOwn"); + AssertEqual(is_own, actual_prop["isOwn"]?.Value() == true, $"{exp_name}#isOwn"); if (exp_prop["__custom_type"]?.Value() == "getter") { @@ -373,14 +363,14 @@ private async Task CheckExpectedProperties(string[] expected_names, Func actual) + private static void AssertHasOnlyExpectedProperties(string[] expected_names, IEnumerable actual) { var exp = new HashSet(expected_names); foreach (var obj in actual) { - if (!exp.Contains(obj["name"]?.Value ())) - Console.WriteLine ($"Unexpected: {obj}"); + if (!exp.Contains(obj["name"]?.Value())) + Console.WriteLine($"Unexpected: {obj}"); } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs index 576c461c18018..ef36ace6624a1 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/HarnessTests.cs @@ -12,7 +12,7 @@ namespace DebuggerTests { - public class HarnessTests : SingleSessionTestBase + public class HarnessTests : DebuggerTestBase { [Fact] public async Task TimedOutWaitingForInvalidBreakpoint() @@ -45,10 +45,10 @@ public async Task InspectorWaitForAfterMessageAlreadyReceived() Result res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); Assert.True(res.IsOk, $"setBreakpoint failed with {res}"); - res = await ctx.cli.SendCommand( + res = await cli.SendCommand( "Runtime.evaluate", JObject.FromObject(new { expression = "window.setTimeout(function() { invoke_add(); }, 0);" }), - ctx.token); + token); Assert.True(res.IsOk, $"evaluating the function failed with {res}"); // delay, so that we can get the Debugger.pause event diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs index fe526ab06a3cd..8c9edc677cc45 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -17,6 +18,9 @@ namespace DebuggerTests { class Inspector { + // https://console.spec.whatwg.org/#formatting-specifiers + private static Regex _consoleArgsRegex = new(@"(%[sdifoOc])", RegexOptions.Compiled); + private const int DefaultTestTimeoutMs = 1 * 60 * 1000; Dictionary> notifications = new Dictionary>(); @@ -106,6 +110,40 @@ void FailAllWaiters(Exception? exception = null) } } + private static string FormatConsoleAPICalled(JObject args) + { + string? type = args?["type"]?.Value(); + List consoleArgs = new(); + foreach (JToken? arg in args?["args"] ?? Enumerable.Empty()) + { + if (arg?["value"] != null) + consoleArgs.Add(arg!["value"]!.ToString()); + } + + int position = 1; + string first = consoleArgs[0]; + string output = _consoleArgsRegex.Replace(first, (_) => $"{consoleArgs[position++]}"); + if (position == 1) + { + // first arg wasn't a format string so concat things together + // with a space instead. + StringBuilder builder = new StringBuilder(first); + for (position = 1; position < consoleArgs.Count(); position++) + { + builder.Append(" "); + builder.Append(consoleArgs[position]); + } + output = builder.ToString(); + } + else + { + if (output.Length > 0 && output[^1] == '\n') + output = output[..^1]; + } + + return $"console.{type}: {output}"; + } + async Task OnMessage(string method, JObject args, CancellationToken token) { switch (method) @@ -117,7 +155,7 @@ async Task OnMessage(string method, JObject args, CancellationToken token) NotifyOf(READY, args); break; case "Runtime.consoleAPICalled": - _logger.LogInformation("CWL: {0}", args["args"]); + _logger.LogInformation(FormatConsoleAPICalled(args)); break; } if (eventListeners.TryGetValue(method, out var listener)) @@ -131,40 +169,6 @@ async Task OnMessage(string method, JObject args, CancellationToken token) } } - public async Task Ready(Func? cb = null, TimeSpan? span = null) - { - try - { - Func)>> fn = (_client, _token) => - { - Func)> getInitCmdFn = (cmd_name) => (cmd_name, _client.SendCommand(cmd_name, null, _token)); - return new List<(string, Task)> - { - getInitCmdFn("Profiler.enable"), - getInitCmdFn("Runtime.enable"), - getInitCmdFn("Debugger.enable"), - getInitCmdFn("Runtime.runIfWaitingForDebugger") - }; - }; - - await OpenSessionAsync(fn, span); - if (cb != null) - await cb(Client, _cancellationTokenSource.Token).ConfigureAwait(false); - } - catch (Exception ex) - { - if (_logger != null) - _logger.LogError(ex.ToString()); - else - Console.WriteLine(ex); - throw; - } - finally - { - await ShutdownAsync().ConfigureAwait(false); - } - } - public async Task OpenSessionAsync(Func)>> getInitCmds, TimeSpan? span = null) { var start = DateTime.Now; @@ -263,7 +267,6 @@ public async Task ShutdownAsync() try { - _logger?.LogDebug($"- test done,. let's close the client"); await Client.Shutdown(_cancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs index 892fe613f591c..e2b796062e308 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MonoJsTests.cs @@ -1,3 +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.IO; using System.Linq; @@ -13,146 +16,111 @@ public class MonoJsTests : DebuggerTestBase [Fact] public async Task FixupNameValueObjectsWithMissingParts() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - await Ready(); - await insp.Ready(async (cli, token) => + var names = new JObject[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - - var names = new JObject[] - { JObject.FromObject(new { name = "Abc" }), JObject.FromObject(new { name = "Def" }), JObject.FromObject(new { name = "Xyz" }) - }; + }; - var values = new JObject[] - { + var values = new JObject[] + { JObject.FromObject(new { value = TObject("testclass") }), JObject.FromObject(new { value = TString("test string") }), - }; + }; - var getters = new JObject[] - { + var getters = new JObject[] + { GetterRes("xyz"), GetterRes("unattached") - }; + }; - var list = new[] { names[0], names[1], values[0], names[2], getters[0], getters[1] }; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression = $"MONO._fixup_name_value_objects({JsonConvert.SerializeObject(list)})", returnByValue = true }), ctx.token); - Assert.True(res.IsOk); + var list = new[] { names[0], names[1], values[0], names[2], getters[0], getters[1] }; + var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression = $"MONO._fixup_name_value_objects({JsonConvert.SerializeObject(list)})", returnByValue = true }), token); + Assert.True(res.IsOk); - await CheckProps(res.Value["result"]["value"], new - { - Abc = TSymbol(""), - Def = TObject("testclass"), - Xyz = TGetter("xyz") - }, "#1", num_fields: 4); + await CheckProps(res.Value["result"]["value"], new + { + Abc = TSymbol(""), + Def = TObject("testclass"), + Xyz = TGetter("xyz") + }, "#1", num_fields: 4); - JObject.DeepEquals(getters[1], res.Value["result"]["value"].Values().ToArray()[3]); + JObject.DeepEquals(getters[1], res.Value["result"]["value"].Values().ToArray()[3]); - JObject GetterRes(string name) => JObject.FromObject(new + static JObject GetterRes(string name) => JObject.FromObject(new + { + get = new { - get = new - { - className = "Function", - description = $"get {name} () {{}}", - type = "function" - } - }); + className = "Function", + description = $"get {name} () {{}}", + type = "function" + } }); } [Fact] public async Task GetParamsAndLocalsWithInvalidIndices() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", - null, -1, -1, "IntAdd"); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", + null, -1, -1, "IntAdd"); - var scope_id = pause_location["callFrames"][0]["callFrameId"].Value(); - var scope = int.Parse(scope_id.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[2]); + var scope_id = pause_location["callFrames"][0]["callFrameId"].Value(); + var scope = int.Parse(scope_id.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[2]); - var var_ids = new[] - { + var var_ids = new[] + { new { index = 0, name = "one" }, new { index = -12, name = "bad0" }, new { index = 1231, name = "bad1" } }; - var expression = $"MONO.mono_wasm_get_variables({scope}, {JsonConvert.SerializeObject(var_ids)})"; + var expression = $"MONO.mono_wasm_get_variables({scope}, {JsonConvert.SerializeObject(var_ids)})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.True(res.IsOk); + var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.True(res.IsOk); - await CheckProps(res.Value["result"]?["value"], new - { - one = TNumber(3), - bad0 = TSymbol(""), - bad1 = TSymbol("") - }, "results"); - }); + await CheckProps(res.Value["result"]?["value"], new + { + one = TNumber(3), + bad0 = TSymbol(""), + bad1 = TSymbol("") + }, "results"); } [Fact] public async Task InvalidScopeId() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); + var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", + null, -1, -1, "IntAdd"); - await Ready(); - await insp.Ready(async (cli, token) => + var var_ids = new[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpointInMethod("debugger-test.dll", "Math", "IntAdd", 3); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method('[debugger-test] Math:IntAdd', 1, 2); })", - null, -1, -1, "IntAdd"); - - var var_ids = new[] - { new { index = 0, name = "one" }, }; - var scope_id = "-12"; - var expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.False(res.IsOk); + var scope_id = "-12"; + var expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.False(res.IsOk); - scope_id = "30000"; - expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; - res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), ctx.token); - Assert.False(res.IsOk); - }); + scope_id = "30000"; + expression = $"MONO.mono_wasm_get_variables({scope_id}, {JsonConvert.SerializeObject(var_ids)})"; + res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression, returnByValue = true }), token); + Assert.False(res.IsOk); } [Fact] public async Task BadRaiseDebugEventsTest() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var bad_expressions = new[] { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bad_expressions = new[] - { "MONO.mono_wasm_raise_debug_event('')", "MONO.mono_wasm_raise_debug_event(undefined)", "MONO.mono_wasm_raise_debug_event({})", @@ -161,17 +129,16 @@ await insp.Ready(async (cli, token) => "MONO.mono_wasm_raise_debug_event({eventName:'foo'}, 12)" }; - foreach (var expression in bad_expressions) - { - var res = await ctx.cli.SendCommand($"Runtime.evaluate", - JObject.FromObject(new - { - expression, - returnByValue = true - }), ctx.token); - Assert.False(res.IsOk, $"Expected to fail for {expression}"); - } - }); + foreach (var expression in bad_expressions) + { + var res = await cli.SendCommand($"Runtime.evaluate", + JObject.FromObject(new + { + expression, + returnByValue = true + }), token); + Assert.False(res.IsOk, $"Expected to fail for {expression}"); + } } [Theory] @@ -180,38 +147,30 @@ await insp.Ready(async (cli, token) => [InlineData(null)] public async Task RaiseDebugEventTraceTest(bool? trace) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var tcs = new TaskCompletionSource(); + insp.On("Runtime.consoleAPICalled", async (args, token) => { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var tcs = new TaskCompletionSource(); - insp.On("Runtime.consoleAPICalled", async (args, token) => { - if (args?["type"]?.Value() == "debug" && - args?["args"]?.Type == JTokenType.Array && - args?["args"]?[0]?["value"]?.Value()?.StartsWith("mono_wasm_debug_event_raised:") == true) - { - tcs.SetResult(true); - } + if (args?["type"]?.Value() == "debug" && + args?["args"]?.Type == JTokenType.Array && + args?["args"]?[0]?["value"]?.Value()?.StartsWith("mono_wasm_debug_event_raised:") == true) + { + tcs.SetResult(true); + } - await Task.CompletedTask; - }); + await Task.CompletedTask; + }); - var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty; - var expression = $"MONO.mono_wasm_raise_debug_event({{ eventName:'qwe' }}, {{ {trace_str} }})"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + var trace_str = trace.HasValue ? $"trace: {trace.ToString().ToLower()}" : String.Empty; + var expression = $"MONO.mono_wasm_raise_debug_event({{ eventName:'qwe' }}, {{ {trace_str} }})"; + var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); - if (trace == true) - Assert.True(tcs.Task == t, "Timed out waiting for the event to be logged"); - else - Assert.False(tcs.Task == t, "Event should not have been logged"); - }); + if (trace == true) + Assert.True(tcs.Task == t, "Timed out waiting for the event to be logged"); + else + Assert.False(tcs.Task == t, "Event should not have been logged"); } [Theory] @@ -250,8 +209,6 @@ public async Task DuplicateAssemblyLoadedEventWithEmbeddedPdbNotLoadedFromBundle async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_path, string source_file, int expected_count) { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); int event_count = 0; var tcs = new TaskCompletionSource(); @@ -262,7 +219,7 @@ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_ var url = args["url"]?.Value(); if (url?.EndsWith(source_file) == true) { - event_count ++; + event_count++; if (event_count > expected_count) tcs.SetResult(false); } @@ -275,40 +232,34 @@ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_ await Task.CompletedTask; }); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - byte[] bytes = File.ReadAllBytes(asm_path); - string asm_base64 = Convert.ToBase64String(bytes); + byte[] bytes = File.ReadAllBytes(asm_path); + string asm_base64 = Convert.ToBase64String(bytes); - string pdb_base64 = String.Empty; - if (pdb_path != null) - { - bytes = File.ReadAllBytes(pdb_path); - pdb_base64 = Convert.ToBase64String(bytes); - } + string pdb_base64 = String.Empty; + if (pdb_path != null) + { + bytes = File.ReadAllBytes(pdb_path); + pdb_base64 = Convert.ToBase64String(bytes); + } - var expression = $@"MONO.mono_wasm_raise_debug_event({{ + var expression = $@"MONO.mono_wasm_raise_debug_event({{ eventName: 'AssemblyLoaded', assembly_name: '{asm_name}', assembly_b64: '{asm_base64}', pdb_b64: '{pdb_base64}' }});"; - var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + var res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); - Assert.True(res.IsOk, $"Expected to pass for {expression}"); + res = await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); + Assert.True(res.IsOk, $"Expected to pass for {expression}"); - var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); - if (t.IsFaulted) - throw t.Exception; + var t = await Task.WhenAny(tcs.Task, Task.Delay(2000)); + if (t.IsFaulted) + throw t.Exception; - Assert.True(event_count <= expected_count, $"number of scriptParsed events received. Expected: {expected_count}, Actual: {event_count}"); - }); + Assert.True(event_count <= expected_count, $"number of scriptParsed events received. Expected: {expected_count}, Actual: {event_count}"); } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/SingleSessionTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/SingleSessionTestBase.cs deleted file mode 100644 index ec765d575bb6f..0000000000000 --- a/src/mono/wasm/debugger/DebuggerTestSuite/SingleSessionTestBase.cs +++ /dev/null @@ -1,49 +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.Threading; -using System.Threading.Tasks; -using Microsoft.WebAssembly.Diagnostics; -using Xunit; - -#nullable enable - -namespace DebuggerTests -{ - public abstract class SingleSessionTestBase : DebuggerTestBase, IAsyncLifetime - { - internal Inspector insp; - protected Dictionary scripts; - - public SingleSessionTestBase(string driver = "debugger-driver.html") : base(driver) - { - insp = new Inspector(); - scripts = SubscribeToScripts(insp); - } - - public virtual async Task InitializeAsync() - { - Func)>> fn = (client, token) => - { - Func)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token)); - var init_cmds = new List<(string, Task)> - { - getInitCmdFn("Profiler.enable"), - getInitCmdFn("Runtime.enable"), - getInitCmdFn("Debugger.enable"), - getInitCmdFn("Runtime.runIfWaitingForDebugger") - }; - - return init_cmds; - }; - - await Ready(); - await insp.OpenSessionAsync(fn); - ctx = new DebugTestContext(insp.Client, insp, insp.Token, scripts); - } - - public virtual async Task DisposeAsync() => await insp.ShutdownAsync().ConfigureAwait(false); - } -} diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs index d966320ded0f6..a5da24b369103 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs @@ -1,3 +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.Linq; using System.Threading.Tasks; @@ -6,7 +9,7 @@ namespace DebuggerTests { - public class SteppingTests : SingleSessionTestBase + public class SteppingTests : DebuggerTestBase { [Fact] public async Task TrivalStepping() @@ -86,7 +89,7 @@ await StepAndCheck(StepKind.Over, debugger_test_loc, 12, 8, "IntAdd", [InlineData(true)] public async Task InspectLocalsInPreviousFramesDuringSteppingIn2(bool use_cfo) { - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; var dep_cs_loc = "dotnet://debugger-test.dll/dependency.cs"; await SetBreakpoint(dep_cs_loc, 33, 8); @@ -153,7 +156,7 @@ public async Task InspectLocalsInPreviousFramesDuringSteppingIn2(bool use_cfo) [InlineData(true)] public async Task InspectLocalsInPreviousFramesDuringSteppingIn(bool use_cfo) { - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; await SetBreakpoint(debugger_test_loc, 111, 12); @@ -313,7 +316,7 @@ await StepAndCheck(StepKind.Over, "dotnet://debugger-test.dll/debugger-test.cs", [InlineData(true)] public async Task InspectLocalsInAsyncMethods(bool use_cfo) { - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; var debugger_test_loc = "dotnet://debugger-test.dll/debugger-test.cs"; await SetBreakpoint(debugger_test_loc, 120, 12); @@ -369,7 +372,7 @@ public async Task InspectLocalsInAsyncMethods(bool use_cfo) [InlineData(true)] public async Task InspectValueTypeMethodArgsWhileStepping(bool use_cfo) { - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; + UseCallFunctionOnBeforeGetProperties = use_cfo; var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; await SetBreakpoint(debugger_test_loc, 36, 12); @@ -610,7 +613,7 @@ public async Task SteppingIntoMscorlib() AssertEqual("WriteLine", top_frame["functionName"]?.Value(), "Expected to be in WriteLine method"); var script_id = top_frame["functionLocation"]["scriptId"].Value(); - Assert.Matches ("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]); + Assert.Matches("^dotnet://(mscorlib|System\\.Console)\\.dll/Console.cs", scripts[script_id]); } [Fact] @@ -620,7 +623,7 @@ public async Task CreateGoodBreakpointAndHitAndRemoveAndDontHit() var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); var pause_location = await EvaluateAndCheck( "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd"); Assert.Equal("other", pause_location["reason"]?.Value()); @@ -638,7 +641,7 @@ public async Task CreateGoodBreakpointAndHitAndRemoveTwice() var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); var pause_location = await EvaluateAndCheck( "window.setTimeout(function() { invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd"); Assert.Equal("other", pause_location["reason"]?.Value()); @@ -655,7 +658,7 @@ public async Task CreateGoodBreakpointAndHitAndRemoveAndDontHitAndCreateAgainAnd var bp2 = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 12, 8); var pause_location = await EvaluateAndCheck( "window.setTimeout(function() { invoke_add(); invoke_add(); invoke_add(); invoke_add()}, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, "IntAdd"); Assert.Equal("other", pause_location["reason"]?.Value()); @@ -845,7 +848,7 @@ await EvaluateAndCheck( "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);", "dotnet://debugger-test.dll/debugger-test.cs", 546, 4, "StepOverHiddenSP2"); - } + } [Fact] public async Task BreakpointOnHiddenLineOfMethodWithNoNextVisibleLineShouldNotPause() @@ -853,7 +856,7 @@ public async Task BreakpointOnHiddenLineOfMethodWithNoNextVisibleLineShouldNotPa await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 554, 12); string expression = "window.setTimeout(function() { invoke_static_method ('[debugger-test] HiddenSequencePointTest:StepOverHiddenSP'); }, 1);"; - await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token); + await cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), token); Task pause_task = insp.WaitFor(Inspector.PAUSE); Task t = await Task.WhenAny(pause_task, Task.Delay(2000)); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs index 84a313f5dd7f7..1b7657ddb87cd 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs @@ -18,15 +18,8 @@ public class SourceList : DebuggerTestBase { [Fact] - public async Task CheckThatAllSourcesAreSent() + public void CheckThatAllSourcesAreSent() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - //all sources are sent before runtime ready is sent, nothing to check - await insp.Ready(); Assert.Contains("dotnet://debugger-test.dll/debugger-test.cs", scripts.Values); Assert.Contains("dotnet://debugger-test.dll/debugger-test2.cs", scripts.Values); Assert.Contains("dotnet://debugger-test.dll/dependency.cs", scripts.Values); @@ -35,106 +28,74 @@ public async Task CheckThatAllSourcesAreSent() [Fact] public async Task CreateGoodBreakpoint() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + Assert.EndsWith("debugger-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc = bp1_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc["scriptId"]); - Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); - Assert.Equal(10, loc["lineNumber"]); - Assert.Equal(8, loc["columnNumber"]); - }); + Assert.NotNull(loc["scriptId"]); + Assert.Equal("dotnet://debugger-test.dll/debugger-test.cs", scripts[loc["scriptId"]?.Value()]); + Assert.Equal(10, loc["lineNumber"]); + Assert.Equal(8, loc["columnNumber"]); } [Fact] public async Task CreateJSBreakpoint() { // Test that js breakpoints get set correctly - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 24); - Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc = bp1_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc["scriptId"]); - Assert.Equal(13, loc["lineNumber"]); - Assert.Equal(24, loc["columnNumber"]); + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); - var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); - Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - var loc2 = bp2_res.Value["locations"]?.Value()[0]; + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - Assert.NotNull(loc2["scriptId"]); - Assert.Equal(13, loc2["lineNumber"]); - Assert.Equal(31, loc2["columnNumber"]); - }); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); } [Fact] public async Task CreateJS0Breakpoint() { - // Test that js column 0 does as expected - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); + // 13 24 + // 13 31 + var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - // 13 24 - // 13 31 - var bp1_res = await SetBreakpoint("/debugger-driver.html", 13, 0); + Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - Assert.EndsWith("debugger-driver.html", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + var loc = bp1_res.Value["locations"]?.Value()[0]; - var loc = bp1_res.Value["locations"]?.Value()[0]; + Assert.NotNull(loc["scriptId"]); + Assert.Equal(13, loc["lineNumber"]); + Assert.Equal(24, loc["columnNumber"]); - Assert.NotNull(loc["scriptId"]); - Assert.Equal(13, loc["lineNumber"]); - Assert.Equal(24, loc["columnNumber"]); + var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); - var bp2_res = await SetBreakpoint("/debugger-driver.html", 13, 31); + Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - Assert.EndsWith("debugger-driver.html", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - var loc2 = bp2_res.Value["locations"]?.Value()[0]; - - Assert.NotNull(loc2["scriptId"]); - Assert.Equal(13, loc2["lineNumber"]); - Assert.Equal(31, loc2["columnNumber"]); - }); + Assert.NotNull(loc2["scriptId"]); + Assert.Equal(13, loc2["lineNumber"]); + Assert.Equal(31, loc2["columnNumber"]); } [Theory] @@ -142,160 +103,109 @@ await insp.Ready(async (cli, token) => [InlineData(50)] public async Task CheckMultipleBreakpointsOnSameLine(int col) { - var insp = new Inspector(); - - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col); - Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); + var bp1_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, col); + Assert.EndsWith("debugger-array-test.cs", bp1_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp1_res.Value["locations"]?.Value()?.Count); - var loc = bp1_res.Value["locations"]?.Value()[0]; + var loc = bp1_res.Value["locations"]?.Value()[0]; - CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc); + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 50, scripts, loc); - var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55); - Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); - Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); + var bp2_res = await SetBreakpoint("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55); + Assert.EndsWith("debugger-array-test.cs", bp2_res.Value["breakpointId"].ToString()); + Assert.Equal(1, bp2_res.Value["locations"]?.Value()?.Count); - var loc2 = bp2_res.Value["locations"]?.Value()[0]; + var loc2 = bp2_res.Value["locations"]?.Value()[0]; - CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2); - }); + CheckLocation("dotnet://debugger-test.dll/debugger-array-test.cs", 219, 55, scripts, loc2); } [Fact] public async Task CreateBadBreakpoint() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var bp1_req = JObject.FromObject(new { - var bp1_req = JObject.FromObject(new - { - lineNumber = 8, - columnNumber = 2, - url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", - }); + lineNumber = 8, + columnNumber = 2, + url = "dotnet://debugger-test.dll/this-file-doesnt-exist.cs", + }); - var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); + var bp1_res = await cli.SendCommand("Debugger.setBreakpointByUrl", bp1_req, token); - Assert.True(bp1_res.IsOk); - Assert.Empty(bp1_res.Value["locations"].Values()); - //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); - }); + Assert.True(bp1_res.IsOk); + Assert.Empty(bp1_res.Value["locations"].Values()); + //Assert.Equal ((int)MonoErrorCodes.BpNotFound, bp1_res.Error ["code"]?.Value ()); } [Fact] public async Task CreateGoodBreakpointAndHit() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 10, 8); + expression = "window.setTimeout(function() { invoke_add(); }, 1);", + }); - var eval_req = JObject.FromObject(new + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_add(); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, + "IntAdd", + wait_for_event_fn: (pause_location) => { - expression = "window.setTimeout(function() { invoke_add(); }, 1);", - }); - - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_add(); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 10, 8, - "IntAdd", - wait_for_event_fn: (pause_location) => - { - Assert.Equal("other", pause_location["reason"]?.Value()); - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - - var top_frame = pause_location["callFrames"][0]; - Assert.Equal("IntAdd", top_frame["functionName"].Value()); - Assert.Contains("debugger-test.cs", top_frame["url"].Value()); + Assert.Equal("other", pause_location["reason"]?.Value()); + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); + var top_frame = pause_location["callFrames"][0]; + Assert.Equal("IntAdd", top_frame["functionName"].Value()); + Assert.Contains("debugger-test.cs", top_frame["url"].Value()); - //now check the scope - var scope = top_frame["scopeChain"][0]; - Assert.Equal("local", scope["type"]); - Assert.Equal("IntAdd", scope["name"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, top_frame["functionLocation"]); - Assert.Equal("object", scope["object"]["type"]); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]); - CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]); - return Task.CompletedTask; - } - ); + //now check the scope + var scope = top_frame["scopeChain"][0]; + Assert.Equal("local", scope["type"]); + Assert.Equal("IntAdd", scope["name"]); - }); + Assert.Equal("object", scope["object"]["type"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 8, 4, scripts, scope["startLocation"]); + CheckLocation("dotnet://debugger-test.dll/debugger-test.cs", 14, 4, scripts, scope["endLocation"]); + return Task.CompletedTask; + } + ); } [Fact] public async Task ExceptionThrownInJS() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - var eval_req = JObject.FromObject(new - { - expression = "invoke_bad_js_test();" - }); - - var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); - Assert.True(eval_res.IsErr); - Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value()); + expression = "invoke_bad_js_test();" }); + + var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); + Assert.True(eval_res.IsErr); + Assert.Equal("Uncaught", eval_res.Error["exceptionDetails"]?["text"]?.Value()); } [Fact] public async Task ExceptionThrownInJSOutOfBand() { - var insp = new Inspector(); - - //Collect events - var scripts = SubscribeToScripts(insp); + await SetBreakpoint("/debugger-driver.html", 27, 2); - await Ready(); - await insp.Ready(async (cli, token) => + var eval_req = JObject.FromObject(new { - ctx = new DebugTestContext(cli, insp, token, scripts); - - await SetBreakpoint("/debugger-driver.html", 27, 2); - - var eval_req = JObject.FromObject(new - { - expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", - }); + expression = "window.setTimeout(function() { invoke_bad_js_test(); }, 1);", + }); - var task = insp.WaitFor("Runtime.exceptionThrown"); - var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); - // Response here will be the id for the timer from JS! - Assert.True(eval_res.IsOk); + var task = insp.WaitFor("Runtime.exceptionThrown"); + var eval_res = await cli.SendCommand("Runtime.evaluate", eval_req, token); + // Response here will be the id for the timer from JS! + Assert.True(eval_res.IsOk); - var ex = await Assert.ThrowsAsync(async () => await task); - var ex_json = JObject.Parse(ex.Message); - Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); - }); + var ex = await Assert.ThrowsAsync(async () => await task); + var ex_json = JObject.Parse(ex.Message); + Assert.Equal(dicFileToUrl["/debugger-driver.html"], ex_json["exceptionDetails"]?["url"]?.Value()); } [Theory] @@ -409,25 +319,25 @@ public async Task InspectNullableLocals(string method_name, bool is_async) => aw var dt = new DateTime(2310, 1, 2, 3, 4, 5); await CheckProps(locals, new { - n_int = TNumber(5), - n_int_null = TObject("System.Nullable", null), + n_int = TNumber(5), + n_int_null = TObject("System.Nullable", null), - n_dt = TDateTime(dt), - n_dt_null = TObject("System.Nullable", null), + n_dt = TDateTime(dt), + n_dt_null = TObject("System.Nullable", null), - n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - n_gs_null = TObject("System.Nullable>", null), + n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + n_gs_null = TObject("System.Nullable>", null), }, "locals"); // check gs var n_gs = GetAndAssertObjectWithName(locals, "n_gs"); - var n_gs_props = await GetProperties(n_gs["value"]?["objectId"]?.Value ()); + var n_gs_props = await GetProperties(n_gs["value"]?["objectId"]?.Value()); await CheckProps(n_gs_props, new { - List = TObject("System.Collections.Generic.List", is_null: true), + List = TObject("System.Collections.Generic.List", is_null: true), StringField = TString("n_gs#StringField"), - Options = TEnum ("DebuggerTests.Options", "None") + Options = TEnum("DebuggerTests.Options", "None") }, nameof(n_gs)); }); @@ -459,41 +369,31 @@ await CheckInspectLocalsAtBreakpointSite( [Fact] public async Task RuntimeGetPropertiesWithInvalidScopeIdTest() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8); + var bp = await SetBreakpoint("dotnet://debugger-test.dll/debugger-test.cs", 49, 8); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_delegates_test (); }, 1);", - "dotnet://debugger-test.dll/debugger-test.cs", 49, 8, - "DelegatesTest", - wait_for_event_fn: async (pause_location) => - { - //make sure we're on the right bp - Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_delegates_test (); }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 49, 8, + "DelegatesTest", + wait_for_event_fn: async (pause_location) => + { + //make sure we're on the right bp + Assert.Equal(bp.Value["breakpointId"]?.ToString(), pause_location["hitBreakpoints"]?[0]?.Value()); - var top_frame = pause_location["callFrames"][0]; + var top_frame = pause_location["callFrames"][0]; - var scope = top_frame["scopeChain"][0]; + var scope = top_frame["scopeChain"][0]; - // Try to get an invalid scope! - var get_prop_req = JObject.FromObject(new - { - objectId = "dotnet:scope:23490871", - }); + // Try to get an invalid scope! + var get_prop_req = JObject.FromObject(new + { + objectId = "dotnet:scope:23490871", + }); - var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); - Assert.True(frame_props.IsErr); - } - ); - }); + var frame_props = await cli.SendCommand("Runtime.getProperties", get_prop_req, token); + Assert.True(frame_props.IsErr); + } + ); } [Theory] @@ -501,99 +401,90 @@ await EvaluateAndCheck( [InlineData(true)] public async Task InspectLocalsWithStructs(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - - await SetBreakpoint(debugger_test_loc, 24, 8); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", - debugger_test_loc, 24, 8, "MethodWithLocalStructs"); - - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckProps(locals, new - { - ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - vt_local = TObject("DebuggerTests.ValueTypesTest") - }, "locals"); + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - var dt = new DateTime(2021, 2, 3, 4, 6, 7); - var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local"); - Assert.Equal(5, vt_local_props.Count()); + await SetBreakpoint(debugger_test_loc, 24, 8); - CheckString(vt_local_props, "StringField", "string#0"); - CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); - CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); - await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5)); - CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_method_with_structs(); }, 1);", + debugger_test_loc, 24, 8, "MethodWithLocalStructs"); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); - await CheckProps(ss_local_props, new - { - V = TGetter("V"), - str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Utc") - }, "ss_local"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TValueType("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + vt_local = TObject("DebuggerTests.ValueTypesTest") + }, "locals"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + var vt_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "vt_local"); + Assert.Equal(5, vt_local_props.Count()); + + CheckString(vt_local_props, "StringField", "string#0"); + CheckValueType(vt_local_props, "SimpleStructField", "DebuggerTests.ValueTypesTest.SimpleStruct"); + CheckValueType(vt_local_props, "SimpleStructProperty", "DebuggerTests.ValueTypesTest.SimpleStruct"); + await CheckDateTime(vt_local_props, "DT", new DateTime(2020, 1, 2, 3, 4, 5)); + CheckEnum(vt_local_props, "RGB", "DebuggerTests.RGB", "Blue"); + + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructs#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); - { - var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - // Check ss_local.gs - var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); - CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); - CheckObject(gs_props, "List", "System.Collections.Generic.List"); - } + // Check ss_local.gs + var gs_props = await GetObjectOnLocals(ss_local_props, "gs"); + CheckString(gs_props, "StringField", "set in MethodWithLocalStructs#SimpleStruct#gs#StringField"); + CheckObject(gs_props, "List", "System.Collections.Generic.List"); + } - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); - await CheckProps(gs_local_props, new - { - StringField = TString("gs_local#GenericStruct#StringField"), - List = TObject("System.Collections.Generic.List", is_null: true), - Options = TEnum("DebuggerTests.Options", "None") - }, "gs_local"); + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List", is_null: true), + Options = TEnum("DebuggerTests.Options", "None") + }, "gs_local"); - // Check vt_local's properties + // Check vt_local's properties - var exp = new[] - { + var exp = new[] + { ("SimpleStructProperty", 2, "Utc"), ("SimpleStructField", 5, "Local") }; - foreach (var (name, bias, dt_kind) in exp) - { - dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); - await CompareObjectPropertiesFor(vt_local_props, name, - new - { - V = TGetter("V"), - str_member = TString($"{name}#string#0#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", dt_kind) - }, - label: $"vt_local_props.{name}"); - - var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); - } + foreach (var (name, bias, dt_kind) in exp) + { + dt = new DateTime(2020 + bias, 1 + bias, 2 + bias, 3 + bias, 5 + bias, 6 + bias); + await CompareObjectPropertiesFor(vt_local_props, name, + new + { + V = TGetter("V"), + str_member = TString($"{name}#string#0#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", dt_kind) + }, + label: $"vt_local_props.{name}"); + + var gres = await InvokeGetter(GetAndAssertObjectWithName(vt_local_props, name), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + (uint)dt.Month), $"{name}#V"); + } - // FIXME: check ss_local.gs.List's members - }); + // FIXME: check ss_local.gs.List's members } [Theory] @@ -611,33 +502,33 @@ public async Task InspectBoxedLocals(string method_name, bool is_async) => await var dt = new DateTime(2310, 1, 2, 3, 4, 5); await CheckProps(locals, new { - n_i = TNumber(5), - o_i = TNumber(5), - o_n_i = TNumber(5), - o_s = TString("foobar"), - o_obj = TObject("Math"), - - n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - o_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + n_i = TNumber(5), + o_i = TNumber(5), + o_n_i = TNumber(5), + o_s = TString("foobar"), + o_obj = TObject("Math"), + + n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + o_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), o_n_gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - n_dt = TDateTime(dt), - o_dt = TDateTime(dt), + n_dt = TDateTime(dt), + o_dt = TDateTime(dt), o_n_dt = TDateTime(dt), o_null = TObject("object", is_null: true), - o_ia = TArray("int[]", 2), + o_ia = TArray("int[]", 2), }, "locals"); foreach (var name in new[] { "n_gs", "o_gs", "o_n_gs" }) { var gs = GetAndAssertObjectWithName(locals, name); - var gs_props = await GetProperties(gs["value"]?["objectId"]?.Value ()); + var gs_props = await GetProperties(gs["value"]?["objectId"]?.Value()); await CheckProps(gs_props, new { - List = TObject("System.Collections.Generic.List", is_null: true), + List = TObject("System.Collections.Generic.List", is_null: true), StringField = TString("n_gs#StringField"), - Options = TEnum ("DebuggerTests.Options", "None") + Options = TEnum("DebuggerTests.Options", "None") }, name); } @@ -688,14 +579,14 @@ public async Task InspectBoxedAsClassLocals(string method_name, bool is_async) = { var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); var dt = new DateTime(2310, 1, 2, 3, 4, 5); - Console.WriteLine (locals); + Console.WriteLine(locals); await CheckProps(locals, new { vt_dt = TDateTime(new DateTime(4819, 5, 6, 7, 8, 9)), vt_gs = TValueType("Math.GenericStruct"), - e = TEnum("System.IO.FileMode", "0"), - ee = TEnum("System.IO.FileMode", "Append") + e = TEnum("System.IO.FileMode", "0"), + ee = TEnum("System.IO.FileMode", "Append") }, "locals"); }); @@ -704,72 +595,63 @@ public async Task InspectBoxedAsClassLocals(string method_name, bool is_async) = [InlineData(true)] public async Task InspectLocalsWithStructsStaticAsync(bool use_cfo) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); + UseCallFunctionOnBeforeGetProperties = use_cfo; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - ctx.UseCallFunctionOnBeforeGetProperties = use_cfo; - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + await SetBreakpoint(debugger_test_loc, 54, 12); - await SetBreakpoint(debugger_test_loc, 54, 12); + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async (" + + "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" + + "); }, 1);", + debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async (" + - "'[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalStructsStaticAsync'" + - "); }, 1);", - debugger_test_loc, 54, 12, "MoveNext"); //BUG: method name + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + await CheckProps(locals, new + { + ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), + gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + result = TBool(true) + }, + "locals#0"); + + var dt = new DateTime(2021, 2, 3, 4, 6, 7); + // Check ss_local's properties + var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); + await CheckProps(ss_local_props, new + { + V = TGetter("V"), + str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), + dt = TDateTime(dt), + gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), + Kind = TEnum("System.DateTimeKind", "Utc") + }, "ss_local"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - await CheckProps(locals, new - { - ss_local = TObject("DebuggerTests.ValueTypesTest.SimpleStruct"), - gs_local = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - result = TBool(true) - }, - "locals#0"); - - var dt = new DateTime(2021, 2, 3, 4, 6, 7); - // Check ss_local's properties - var ss_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "ss_local"); - await CheckProps(ss_local_props, new - { - V = TGetter("V"), - str_member = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#str_member"), - dt = TDateTime(dt), - gs = TValueType("DebuggerTests.ValueTypesTest.GenericStruct"), - Kind = TEnum("System.DateTimeKind", "Utc") - }, "ss_local"); + { + var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); + await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - { - var gres = await InvokeGetter(GetAndAssertObjectWithName(locals, "ss_local"), "V"); - await CheckValue(gres.Value["result"], TNumber(0xDEADBEEF + 2), $"ss_local#V"); - - // Check ss_local.gs - await CompareObjectPropertiesFor(ss_local_props, "gs", - new - { - StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option1") - } - ); - } + // Check ss_local.gs + await CompareObjectPropertiesFor(ss_local_props, "gs", + new + { + StringField = TString("set in MethodWithLocalStructsStaticAsync#SimpleStruct#gs#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option1") + } + ); + } - // Check gs_local's properties - var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); - await CheckProps(gs_local_props, new - { - StringField = TString("gs_local#GenericStruct#StringField"), - List = TObject("System.Collections.Generic.List"), - Options = TEnum("DebuggerTests.Options", "Option2") - }, "gs_local"); + // Check gs_local's properties + var gs_local_props = await GetObjectOnFrame(pause_location["callFrames"][0], "gs_local"); + await CheckProps(gs_local_props, new + { + StringField = TString("gs_local#GenericStruct#StringField"), + List = TObject("System.Collections.Generic.List"), + Options = TEnum("DebuggerTests.Options", "Option2") + }, "gs_local"); - // FIXME: check ss_local.gs.List's members - }); + // FIXME: check ss_local.gs.List's members } [Theory] @@ -779,113 +661,95 @@ await CompareObjectPropertiesFor(ss_local_props, "gs", [InlineData(182, 12, "MethodWithArgumentsForToStringTestAsync", false, true)] public async Task InspectLocalsForToStringDescriptions(int line, int col, string method_name, bool call_other, bool invoke_async) { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); string entry_method_name = $"[debugger-test] DebuggerTests.ValueTypesTest:MethodWithLocalsForToStringTest{(invoke_async ? "Async" : String.Empty)}"; int frame_idx = 0; + var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - var debugger_test_loc = "dotnet://debugger-test.dll/debugger-valuetypes-test.cs"; + await SetBreakpoint(debugger_test_loc, line, col); - await SetBreakpoint(debugger_test_loc, line, col); + var eval_expr = "window.setTimeout(function() {" + + (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + + $"'{entry_method_name}'," + + (call_other ? "true" : "false") + + "); }, 1);"; + Console.WriteLine($"{eval_expr}"); - var eval_expr = "window.setTimeout(function() {" + - (invoke_async ? "invoke_static_method_async (" : "invoke_static_method (") + - $"'{entry_method_name}'," + - (call_other ? "true" : "false") + - "); }, 1);"; - Console.WriteLine($"{eval_expr}"); + var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); - var pause_location = await EvaluateAndCheck(eval_expr, debugger_test_loc, line, col, invoke_async ? "MoveNext" : method_name); + var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); + var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); + var ts = dt0 - dt1; + var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); - var dt0 = new DateTime(2020, 1, 2, 3, 4, 5); - var dt1 = new DateTime(2010, 5, 4, 3, 2, 1); - var ts = dt0 - dt1; - var dto = new DateTimeOffset(dt0, new TimeSpan(4, 5, 0)); - - var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); - await CheckProps(frame_locals, new + var frame_locals = await GetProperties(pause_location["callFrames"][frame_idx]["callFrameId"].Value()); + await CheckProps(frame_locals, new + { + call_other = TBool(call_other), + dt0 = TDateTime(dt0), + dt1 = TDateTime(dt1), + dto = TValueType("System.DateTimeOffset", dto.ToString()), + ts = TValueType("System.TimeSpan", ts.ToString()), + dec = TValueType("System.Decimal", "123987123"), + guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), + dts = TArray("System.DateTime[]", 2), + obj = TObject("DebuggerTests.ClassForToStringTests"), + sst = TObject("DebuggerTests.StructForToStringTests") + }, "locals#0"); + + var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10); + var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3); + var dts_elements = await GetObjectOnLocals(frame_locals, "dts"); + await CheckDateTime(dts_elements, "0", dts_0); + await CheckDateTime(dts_elements, "1", dts_1); + + // TimeSpan + await CompareObjectPropertiesFor(frame_locals, "ts", + new { - call_other = TBool(call_other), - dt0 = TDateTime(dt0), - dt1 = TDateTime(dt1), - dto = TValueType("System.DateTimeOffset", dto.ToString()), - ts = TValueType("System.TimeSpan", ts.ToString()), - dec = TValueType("System.Decimal", "123987123"), - guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014"), - dts = TArray("System.DateTime[]", 2), - obj = TObject("DebuggerTests.ClassForToStringTests"), - sst = TObject("DebuggerTests.StructForToStringTests") - }, "locals#0"); - - var dts_0 = new DateTime(1983, 6, 7, 5, 6, 10); - var dts_1 = new DateTime(1999, 10, 15, 1, 2, 3); - var dts_elements = await GetObjectOnLocals(frame_locals, "dts"); - await CheckDateTime(dts_elements, "0", dts_0); - await CheckDateTime(dts_elements, "1", dts_1); - - // TimeSpan - await CompareObjectPropertiesFor(frame_locals, "ts", - new - { - Days = TNumber(3530), - Minutes = TNumber(2), - Seconds = TNumber(4), - }, "ts_props", num_fields: 12); - - // DateTimeOffset - await CompareObjectPropertiesFor(frame_locals, "dto", - new - { - Day = TNumber(2), - Year = TNumber(2020), - DayOfWeek = TEnum("System.DayOfWeek", "Thursday") - }, "dto_props", num_fields: 22); + Days = TNumber(3530), + Minutes = TNumber(2), + Seconds = TNumber(4), + }, "ts_props", num_fields: 12); + + // DateTimeOffset + await CompareObjectPropertiesFor(frame_locals, "dto", + new + { + Day = TNumber(2), + Year = TNumber(2020), + DayOfWeek = TEnum("System.DayOfWeek", "Thursday") + }, "dto_props", num_fields: 22); - var DT = new DateTime(2004, 10, 15, 1, 2, 3); - var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); + var DT = new DateTime(2004, 10, 15, 1, 2, 3); + var DTO = new DateTimeOffset(dt0, new TimeSpan(2, 14, 0)); - await CompareObjectPropertiesFor(frame_locals, "obj", - new - { - DT = TDateTime(DT), - DTO = TValueType("System.DateTimeOffset", DTO.ToString()), - TS = TValueType("System.TimeSpan", ts.ToString()), - Dec = TValueType("System.Decimal", "1239871"), - Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "obj_props"); - - DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)); - var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", - new - { - DT = TDateTime(DT), - DTO = TValueType("System.DateTimeOffset", DTO.ToString()), - TS = TValueType("System.TimeSpan", ts.ToString()), - Dec = TValueType("System.Decimal", "1239871"), - Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") - }, "sst_props"); - }); + await CompareObjectPropertiesFor(frame_locals, "obj", + new + { + DT = TDateTime(DT), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "obj_props"); + + DTO = new DateTimeOffset(dt0, new TimeSpan(3, 15, 0)); + var sst_props = await CompareObjectPropertiesFor(frame_locals, "sst", + new + { + DT = TDateTime(DT), + DTO = TValueType("System.DateTimeOffset", DTO.ToString()), + TS = TValueType("System.TimeSpan", ts.ToString()), + Dec = TValueType("System.Decimal", "1239871"), + Guid = TValueType("System.Guid", "3D36E07E-AC90-48C6-B7EC-A481E289D014") + }, "sst_props"); } [Fact] public async Task InspectLocals() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - var wait_res = await RunUntil("locals_inner"); - var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value()); - }); + var wait_res = await RunUntil("locals_inner"); + var locals = await GetProperties(wait_res["callFrames"][1]["callFrameId"].Value()); } [Theory] @@ -948,7 +812,7 @@ await CheckInspectLocalsAtBreakpointSite( async Task CreateNewId(string expr) { - var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + var res = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); Assert.True(res.IsOk, "Expected Runtime.evaluate to succeed"); AssertEqual("string", res.Value["result"]?["type"]?.Value(), "Expected Runtime.evaluate to return a string type result"); return res.Value["result"]?["value"]?.Value(); @@ -957,7 +821,7 @@ async Task CreateNewId(string expr) async Task _invoke_getter(string obj_id, string property_name, bool expect_ok) { var expr = $"MONO._invoke_getter ('{obj_id}', '{property_name}')"; - var res = await ctx.cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), ctx.token); + var res = await cli.SendCommand("Runtime.evaluate", JObject.FromObject(new { expression = expr }), token); AssertEqual(expect_ok, res.IsOk, "Runtime.evaluate result not as expected for {expr}"); return res; @@ -1057,84 +921,59 @@ JObject FindFrame(JObject pause_location, string function_name) [Fact] public async Task DebugLazyLoadedAssemblyWithPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - 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")); - - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; - Assert.Contains(source_location, scripts.Values); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", - source_location, line, 8, - "IntAdd"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - CheckNumber(locals, "a", 5); - CheckNumber(locals, "b", 10); - }); + 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")); + + var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + Assert.Contains(source_location, scripts.Values); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);", + source_location, line, 8, + "IntAdd"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 5); + CheckNumber(locals, "b", 10); } [Fact] public async Task DebugLazyLoadedAssemblyWithEmbeddedPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - - int line = 9; - await SetBreakpoint(".*/lazy-debugger-test-embedded.cs$", line, 0, use_regex: true); - await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"), - null); - - var source_location = "dotnet://lazy-debugger-test-embedded.dll/lazy-debugger-test-embedded.cs"; - Assert.Contains(source_location, scripts.Values); - - var pause_location = await EvaluateAndCheck( - "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test-embedded] LazyMath:IntAdd', 5, 10); }, 1);", - source_location, line, 8, - "IntAdd"); - var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); - CheckNumber(locals, "a", 5); - CheckNumber(locals, "b", 10); - }); + int line = 9; + await SetBreakpoint(".*/lazy-debugger-test-embedded.cs$", line, 0, use_regex: true); + await LoadAssemblyDynamically( + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"), + null); + + var source_location = "dotnet://lazy-debugger-test-embedded.dll/lazy-debugger-test-embedded.cs"; + Assert.Contains(source_location, scripts.Values); + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test-embedded] LazyMath:IntAdd', 5, 10); }, 1);", + source_location, line, 8, + "IntAdd"); + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + CheckNumber(locals, "a", 5); + CheckNumber(locals, "b", 10); } [Fact] public async Task CannotDebugLazyLoadedAssemblyWithoutPdb() { - var insp = new Inspector(); - var scripts = SubscribeToScripts(insp); - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); + int line = 9; + await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); + await LoadAssemblyDynamically( + Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), + null); - int line = 9; - await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true); - await LoadAssemblyDynamically( - Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"), - null); + // wait to bit to catch if the event might be raised a bit late + await Task.Delay(1000); - // wait to bit to catch if the event might be raised a bit late - await Task.Delay(1000); - - var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; - Assert.DoesNotContain(source_location, scripts.Values); - }); + var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs"; + Assert.DoesNotContain(source_location, scripts.Values); } async Task LoadAssemblyDynamically(string asm_file, string pdb_file) @@ -1144,7 +983,8 @@ async Task LoadAssemblyDynamically(string asm_file, string pdb_file) string asm_base64 = Convert.ToBase64String(bytes); string pdb_base64 = null; - if (pdb_file != null) { + if (pdb_file != null) + { bytes = File.ReadAllBytes(pdb_file); pdb_base64 = Convert.ToBase64String(bytes); } @@ -1154,26 +994,17 @@ async Task LoadAssemblyDynamically(string asm_file, string pdb_file) expression = $"{{ let asm_b64 = '{asm_base64}'; let pdb_b64 = '{pdb_base64}'; invoke_static_method('[debugger-test] LoadDebuggerTest:LoadLazyAssembly', asm_b64, pdb_b64); }}" }); - Result load_assemblies_res = await ctx.cli.SendCommand("Runtime.evaluate", load_assemblies, ctx.token); + Result load_assemblies_res = await cli.SendCommand("Runtime.evaluate", load_assemblies, token); Assert.True(load_assemblies_res.IsOk); } [Fact] public async Task BreakOnDebuggerBreak() { - var insp = new Inspector(); - //Collect events - var scripts = SubscribeToScripts(insp); - - await Ready(); - await insp.Ready(async (cli, token) => - { - ctx = new DebugTestContext(cli, insp, token, scripts); - await EvaluateAndCheck( - "window.setTimeout(function() { invoke_static_method_async('[debugger-test] UserBreak:BreakOnDebuggerBreakCommand'); }, 1);", - "dotnet://debugger-test.dll/debugger-test2.cs", 56, 4, - "BreakOnDebuggerBreakCommand"); - }); + await EvaluateAndCheck( + "window.setTimeout(function() { invoke_static_method_async('[debugger-test] UserBreak:BreakOnDebuggerBreakCommand'); }, 1);", + "dotnet://debugger-test.dll/debugger-test2.cs", 56, 4, + "BreakOnDebuggerBreakCommand"); } //TODO add tests covering basic stepping behavior as step in/out/over } diff --git a/src/mono/wasm/runtime-test.js b/src/mono/wasm/runtime-test.js index 14a59c43c6da8..952598414d21c 100644 --- a/src/mono/wasm/runtime-test.js +++ b/src/mono/wasm/runtime-test.js @@ -391,13 +391,13 @@ var App = { fail_exec ("Unhandled argument: " + args [0]); } }, - call_test_method: function (method_name, args) { - if (arguments.length > 2) + call_test_method: function (method_name, args, signature) { + if ((arguments.length > 2) && (typeof (signature) !== "string")) throw new Error("Invalid number of arguments for call_test_method"); var fqn = "[System.Private.Runtime.InteropServices.JavaScript.Tests]System.Runtime.InteropServices.JavaScript.Tests.HelperMarshal:" + method_name; try { - return BINDING.call_static_method(fqn, args || []); + return BINDING.call_static_method(fqn, args || [], signature); } catch (exc) { console.error("exception thrown in", fqn); throw exc; diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 332bc25710766..34dac476e1ea5 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -25,6 +25,7 @@ var BindingSupportLib = { module ["mono_call_static_method"] = BINDING.call_static_method.bind(BINDING); module ["mono_bind_assembly_entry_point"] = BINDING.bind_assembly_entry_point.bind(BINDING); module ["mono_call_assembly_entry_point"] = BINDING.call_assembly_entry_point.bind(BINDING); + module ["mono_intern_string"] = BINDING.mono_intern_string.bind(BINDING); }, bindings_lazy_init: function () { @@ -66,6 +67,7 @@ var BindingSupportLib = { this.mono_wasm_register_bundled_satellite_assemblies = Module.cwrap ('mono_wasm_register_bundled_satellite_assemblies', 'void', [ ]); this.mono_wasm_try_unbox_primitive_and_get_type = Module.cwrap ('mono_wasm_try_unbox_primitive_and_get_type', 'number', ['number', 'number']); this.mono_wasm_box_primitive = Module.cwrap ('mono_wasm_box_primitive', 'number', ['number', 'number', 'number']); + this.mono_wasm_intern_string = Module.cwrap ('mono_wasm_intern_string', 'number', ['number']); this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']); this._box_buffer = Module._malloc(16); @@ -144,9 +146,108 @@ var BindingSupportLib = { this.safehandle_release_by_handle = get_method ("SafeHandleReleaseByHandle"); this._are_promises_supported = ((typeof Promise === "object") || (typeof Promise === "function")) && (typeof Promise.resolve === "function"); + + this._empty_string = ""; + this._empty_string_ptr = 0; + this._interned_string_full_root_buffers = []; + this._interned_string_current_root_buffer = null; + this._interned_string_current_root_buffer_count = 0; + this._interned_string_table = new Map (); + this._managed_pointer_to_interned_string_table = new Map (); + }, + + // Ensures the string is already interned on both the managed and JavaScript sides, + // then returns the interned string value (to provide fast reference comparisons like C#) + mono_intern_string: function (string) { + if (string.length === 0) + return this._empty_string; + + var ptr = this.js_string_to_mono_string_interned (string); + var result = this._managed_pointer_to_interned_string_table.get (ptr); + return result; + }, + + _store_string_in_intern_table: function (string, ptr, internIt) { + if (!ptr) + throw new Error ("null pointer passed to _store_string_in_intern_table"); + + var originalArg = ptr; + + const internBufferSize = 8192; + + if (this._interned_string_current_root_buffer_count >= internBufferSize) { + this._interned_string_full_root_buffers.push (this._interned_string_current_root_buffer); + this._interned_string_current_root_buffer = null; + } + if (!this._interned_string_current_root_buffer) { + this._interned_string_current_root_buffer = MONO.mono_wasm_new_root_buffer (internBufferSize, "interned strings"); + this._interned_string_current_root_buffer_count = 0; + } + + var rootBuffer = this._interned_string_current_root_buffer; + var index = this._interned_string_current_root_buffer_count++; + rootBuffer.set (index, ptr); + + // Store the managed string into the managed intern table. This can theoretically + // provide a different managed object than the one we passed in, so update our + // pointer (stored in the root) with the result. + if (internIt) + rootBuffer.set (index, ptr = this.mono_wasm_intern_string (ptr)); + + if (!ptr) + throw new Error ("mono_wasm_intern_string produced a null pointer"); + + this._interned_string_table.set (string, ptr); + this._managed_pointer_to_interned_string_table.set (ptr, string); + + if ((string.length === 0) && !this._empty_string_ptr) + this._empty_string_ptr = ptr; + + return ptr; + }, + + js_string_to_mono_string_interned: function (string) { + var text = (typeof (string) === "symbol") + ? (string.description || Symbol.keyFor(string) || "") + : string; + + if ((text.length === 0) && this._empty_string_ptr) + return this._empty_string_ptr; + + var ptr = this._interned_string_table.get (string); + if (ptr) + return ptr; + + ptr = this.js_string_to_mono_string_new (text); + ptr = this._store_string_in_intern_table (string, ptr, true); + + return ptr; }, js_string_to_mono_string: function (string) { + if (typeof (string) === "symbol") + return this.js_string_to_mono_string_interned (string); + else if (typeof (string) !== "string") + throw new Error ("Expected string argument"); + + // Always use an interned pointer for empty strings + if (string.length === 0) + return this.js_string_to_mono_string_interned (string); + + // Looking up large strings in the intern table will require the JS runtime to + // potentially hash them and then do full byte-by-byte comparisons, which is + // very expensive. Because we can not guarantee it won't happen, try to minimize + // the cost of this and prevent performance issues for large strings + if (string.length <= 256) { + var interned = this._interned_string_table.get (string); + if (interned) + return interned; + } + + return this.js_string_to_mono_string_new (string); + }, + + js_string_to_mono_string_new: function (string) { var buffer = Module._malloc ((string.length + 1) * 2); var buffer16 = (buffer / 2) | 0; for (var i = 0; i < string.length; i++) @@ -173,28 +274,23 @@ var BindingSupportLib = { return null; }, - conv_string: function (mono_obj) { - return MONO.string_decoder.copy (mono_obj); + conv_string: function (mono_obj, interned) { + var interned_instance = this._managed_pointer_to_interned_string_table.get (mono_obj); + if (interned_instance !== undefined) + return interned_instance; + + var result = MONO.string_decoder.copy (mono_obj); + if (interned) { + // This string is interned on the managed side but we didn't have it in our cache. + this._store_string_in_intern_table (result, mono_obj, false); + } + return result; }, is_nested_array: function (ele) { return this._is_simple_array(ele); }, - js_string_to_mono_string: function (string) { - if (string === null || typeof string === "undefined") - return 0; - - var buffer = Module._malloc ((string.length + 1) * 2); - var buffer16 = (buffer / 2) | 0; - for (var i = 0; i < string.length; i++) - Module.HEAP16[buffer16 + i] = string.charCodeAt (i); - Module.HEAP16[buffer16 + string.length] = 0; - var result = this.mono_wasm_string_from_utf16 (buffer, string.length); - Module._free (buffer); - return result; - }, - mono_array_to_js_array: function (mono_array) { if (mono_array === 0) return null; @@ -310,8 +406,10 @@ var BindingSupportLib = { case 27: // uint64 // TODO: Fix this once emscripten offers HEAPI64/HEAPU64 or can return them throw new Error ("int64 not available"); - case 3: //string - return this.conv_string (mono_obj); + case 3: // string + return this.conv_string (mono_obj, false); + case 29: // interned string + return this.conv_string (mono_obj, true); case 4: //vts throw new Error ("no idea on how to unbox value types"); case 5: // delegate @@ -451,6 +549,8 @@ var BindingSupportLib = { return result; } case typeof js_obj === "string": return this.js_string_to_mono_string (js_obj); + case typeof js_obj === "symbol": + return this.js_string_to_mono_string_interned (js_obj); case typeof js_obj === "boolean": return this._box_js_bool (js_obj); case isThenable() === true: @@ -480,6 +580,7 @@ var BindingSupportLib = { case js_obj === null: case typeof js_obj === "undefined": return 0; + case typeof js_obj === "symbol": case typeof js_obj === "string": return this.call_method(this.create_uri, null, "s!", [ js_obj ]) default: @@ -807,6 +908,7 @@ var BindingSupportLib = { var result = new Map (); result.set ('m', { steps: [{ }], size: 0}); result.set ('s', { steps: [{ convert: this.js_string_to_mono_string.bind (this) }], size: 0, needs_root: true }); + result.set ('S', { steps: [{ convert: this.js_string_to_mono_string_interned.bind (this) }], size: 0, needs_root: true }); result.set ('o', { steps: [{ convert: this.js_to_mono_obj.bind (this) }], size: 0, needs_root: true }); result.set ('u', { steps: [{ convert: this.js_to_mono_uri.bind (this) }], size: 0, needs_root: true }); @@ -1089,7 +1191,7 @@ var BindingSupportLib = { if (exception === 0) return null; - var msg = this.conv_string (result); + var msg = this.conv_string (result, false); var err = new Error (msg); //the convention is that invoke_method ToString () any outgoing exception // console.warn ("error", msg, "at location", err.stack); return err; @@ -1134,6 +1236,7 @@ var BindingSupportLib = { f: float d: double s: string + S: interned string o: js object will be converted to a C# object (this will box numbers/bool/promises) m: raw mono object. Don't use it unless you know what you're doing @@ -1588,7 +1691,7 @@ var BindingSupportLib = { return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); } - var js_name = BINDING.conv_string (method_name); + var js_name = BINDING.conv_string (method_name, false); if (!js_name) { setValue (is_exception, 1, "i32"); return BINDING.js_string_to_mono_string ("Invalid method name object '" + method_name + "'"); @@ -1622,7 +1725,7 @@ var BindingSupportLib = { return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); } - var js_name = BINDING.conv_string (property_name); + var js_name = BINDING.conv_string (property_name, false); if (!js_name) { setValue (is_exception, 1, "i32"); return BINDING.js_string_to_mono_string ("Invalid property name object '" + js_name + "'"); @@ -1653,7 +1756,7 @@ var BindingSupportLib = { return BINDING.js_string_to_mono_string ("Invalid JS object handle '" + js_handle + "'"); } - var property = BINDING.conv_string (property_name); + var property = BINDING.conv_string (property_name, false); if (!property) { setValue (is_exception, 1, "i32"); return BINDING.js_string_to_mono_string ("Invalid property name object '" + property_name + "'"); @@ -1737,7 +1840,7 @@ var BindingSupportLib = { mono_wasm_get_global_object: function(global_name, is_exception) { BINDING.bindings_lazy_init (); - var js_name = BINDING.conv_string (global_name); + var js_name = BINDING.conv_string (global_name, false); var globalObj; @@ -1795,7 +1898,7 @@ var BindingSupportLib = { mono_wasm_new: function (core_name, args, is_exception) { BINDING.bindings_lazy_init (); - var js_name = BINDING.conv_string (core_name); + var js_name = BINDING.conv_string (core_name, false); if (!js_name) { setValue (is_exception, 1, "i32"); diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index ab7895f0eb3be..fb4f3128c2ad9 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -48,6 +48,7 @@ void mono_icall_table_init (void); void mono_aot_register_module (void **aot_info); char *monoeg_g_getenv(const char *variable); int monoeg_g_setenv(const char *variable, const char *value, int overwrite); +int32_t monoeg_g_hasenv(const char *variable); void mono_free (void*); int32_t mini_parse_debug_option (const char *option); char *mono_method_get_full_name (MonoMethod *method); @@ -466,9 +467,6 @@ EMSCRIPTEN_KEEPALIVE void mono_wasm_load_runtime (const char *unused, int debug_level) { const char *interp_opts = ""; -#ifdef ENABLE_METADATA_UPDATE - interp_opts = "-inline"; /* FIXME: EnC hack - make this configurable */ -#endif #ifdef DEBUG monoeg_g_setenv ("MONO_LOG_LEVEL", "debug", 0); @@ -505,6 +503,12 @@ mono_wasm_load_runtime (const char *unused, int debug_level) #else mono_jit_set_aot_mode (MONO_AOT_MODE_INTERP_ONLY); +#ifdef ENABLE_METADATA_UPDATE + if (monoeg_g_hasenv ("MONO_METADATA_UPDATE")) { + interp_opts = "-inline"; + } +#endif + /* * debug_level > 0 enables debugging and sets the debug log level to debug_level * debug_level == 0 disables debugging and enables interpreter optimizations @@ -517,6 +521,7 @@ mono_wasm_load_runtime (const char *unused, int debug_level) interp_opts = "-all"; mono_wasm_enable_debugging (debug_level); } + #endif #ifdef LINK_ICALLS @@ -775,6 +780,7 @@ MonoClass* mono_get_uri_class(MonoException** exc) #define MARSHAL_TYPE_INT64 26 #define MARSHAL_TYPE_UINT64 27 #define MARSHAL_TYPE_CHAR 28 +#define MARSHAL_TYPE_STRING_INTERNED 29 void mono_wasm_ensure_classes_resolved () { @@ -884,6 +890,12 @@ mono_wasm_get_obj_type (MonoObject *obj) /* Process obj before calling into the runtime, class_from_name () can invoke managed code */ MonoClass *klass = mono_object_get_class (obj); + if ( + (klass == mono_get_string_class ()) && + (mono_string_is_interned (obj) == obj) + ) + return MARSHAL_TYPE_STRING_INTERNED; + MonoType *type = mono_class_get_type (klass); obj = NULL; @@ -907,6 +919,14 @@ mono_wasm_try_unbox_primitive_and_get_type (MonoObject *obj, void *result) /* Process obj before calling into the runtime, class_from_name () can invoke managed code */ MonoClass *klass = mono_object_get_class (obj); + if ( + (klass == mono_get_string_class ()) && + (mono_string_is_interned (obj) == obj) + ) { + *resultL = 0; + return MARSHAL_TYPE_STRING_INTERNED; + } + MonoType *type = mono_class_get_type (klass), *original_type = type; if (mono_class_is_enum (klass)) @@ -1065,3 +1085,9 @@ mono_wasm_enable_on_demand_gc (int enable) { mono_wasm_enable_gc = enable ? 1 : 0; } + +EMSCRIPTEN_KEEPALIVE MonoString * +mono_wasm_intern_string (MonoString *string) +{ + return mono_string_intern (string); +} diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 172f329e82e13..c3a5bb3a075b0 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Collections.Concurrent; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Build.Framework; @@ -103,7 +104,10 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task /// public string? MsymPath { get; set; } - public List FileWrites { get; } = new(); + [Output] + public string[]? FileWrites { get; private set; } + + private List _fileWrites = new(); private ConcurrentBag compiledAssemblies = new ConcurrentBag(); private MonoAotMode parsedAotMode; @@ -196,6 +200,7 @@ public override bool Execute() } CompiledAssemblies = compiledAssemblies.ToArray(); + FileWrites = _fileWrites.ToArray(); return !Log.HasLoggedErrors; } @@ -350,7 +355,7 @@ private void GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers using (var writer = File.CreateText(AotModulesTablePath!)) { - FileWrites.Add(AotModulesTablePath!); + _fileWrites.Add(AotModulesTablePath!); if (parsedAotModulesTableLanguage == MonoAotModulesTableLanguage.C) { foreach (var symbol in symbols) @@ -365,7 +370,7 @@ private void GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers } writer.WriteLine("}"); - foreach (var profiler in profilers!) + foreach (var profiler in profilers ?? Enumerable.Empty()) { writer.WriteLine($"void mono_profiler_init_{profiler} (const char *desc);"); writer.WriteLine("EMSCRIPTEN_KEEPALIVE void mono_wasm_load_profiler_" + profiler + " (const char *desc) { mono_profiler_init_" + profiler + " (desc); }"); @@ -406,6 +411,7 @@ private void GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers { throw new NotSupportedException(); } + Log.LogMessage(MessageImportance.Low, $"Generated {AotModulesTablePath}"); } } } diff --git a/src/tasks/Common/Utils.cs b/src/tasks/Common/Utils.cs index f896b6c7fe7f5..97b8e5cf0e5cf 100644 --- a/src/tasks/Common/Utils.cs +++ b/src/tasks/Common/Utils.cs @@ -68,7 +68,7 @@ public static string RunProcess( { if (!silent) { - LogError(e.Data); + LogWarning(e.Data); outputBuilder.AppendLine(e.Data); } errorBuilder.AppendLine(e.Data); @@ -124,6 +124,12 @@ public static void LogInfo(string? msg, MessageImportance importance=MessageImpo Logger?.LogMessage(importance, msg); } + public static void LogWarning(string? msg) + { + if (msg != null) + Logger?.LogWarning(msg); + } + public static void LogError(string? msg) { if (msg != null) diff --git a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs index c870de4c864bf..d43a09d07e6f9 100644 --- a/src/tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/src/tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -9,6 +9,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; using System.Reflection; @@ -33,8 +34,6 @@ public class WasmAppBuilder : Task [Required] public string[]? Assemblies { get; set; } - public bool EnableProfiler { get; set; } - private List _fileWrites = new(); [Output] @@ -51,6 +50,20 @@ public class WasmAppBuilder : Task public bool InvariantGlobalization { get; set; } public ITaskItem[]? ExtraFilesToDeploy { get; set; } + // + // Extra json elements to add to mono-config.js + // + // Metadata: + // - Value: can be a number, bool, quoted string, or json string + // + // Examples: + // + // + // + // + // + public ITaskItem[]? ExtraConfig { get; set; } + private class WasmAppConfig { [JsonPropertyName("assembly_root")] @@ -61,8 +74,8 @@ private class WasmAppConfig public List Assets { get; } = new List(); [JsonPropertyName("remote_sources")] public List RemoteSources { get; set; } = new List(); - [JsonPropertyName("enable_profiler")] - public bool EnableProfiler { get; set; } = false; + [JsonExtensionData] + public Dictionary Extra { get; set; } = new(); } private class AssetEntry @@ -231,9 +244,14 @@ public override bool Execute () if (source != null && source.ItemSpec != null) config.RemoteSources.Add(source.ItemSpec); } - if (EnableProfiler) + + foreach (ITaskItem extra in ExtraConfig ?? Enumerable.Empty()) { - config.EnableProfiler = true; + string name = extra.ItemSpec; + if (!TryParseExtraConfigValue(extra, out object? valueObject)) + return false; + + config.Extra[name] = valueObject; } string monoConfigPath = Path.Join(AppDir, "mono-config.js"); @@ -263,6 +281,51 @@ public override bool Execute () return true; } + private bool TryParseExtraConfigValue(ITaskItem extraItem, out object? valueObject) + { + valueObject = null; + string? rawValue = extraItem.GetMetadata("Value"); + if (string.IsNullOrEmpty(rawValue)) + return true; + + if (TryConvert(rawValue, typeof(double), out valueObject) || TryConvert(rawValue, typeof(bool), out valueObject)) + return true; + + // Try parsing as a quoted string + if (rawValue!.Length > 1 && rawValue![0] == '"' && rawValue![^1] == '"') + { + valueObject = rawValue![1..^1]; + return true; + } + + // try parsing as json + try + { + JsonDocument jdoc = JsonDocument.Parse(rawValue); + valueObject = jdoc.RootElement; + return true; + } + catch (JsonException je) + { + Log.LogError($"ExtraConfig: {extraItem.ItemSpec} with Value={rawValue} cannot be parsed as a number, boolean, string, or json object/array: {je.Message}"); + return false; + } + } + + private static bool TryConvert(string str, Type type, out object? value) + { + value = null; + try + { + value = Convert.ChangeType(str, type); + return true; + } + catch (Exception ex) when (ex is FormatException || ex is InvalidCastException || ex is OverflowException) + { + return false; + } + } + private bool FileCopyChecked(string src, string dst, string label) { if (!File.Exists(src)) diff --git a/src/tests/FunctionalTests/wasm/Directory.Build.props b/src/tests/FunctionalTests/wasm/Directory.Build.props index cd5f037a937f9..af320d2123e99 100644 --- a/src/tests/FunctionalTests/wasm/Directory.Build.props +++ b/src/tests/FunctionalTests/wasm/Directory.Build.props @@ -3,10 +3,6 @@ Release Exe - - true - link - false diff --git a/src/tests/Interop/COM/ComWrappers/API/Program.cs b/src/tests/Interop/COM/ComWrappers/API/Program.cs index 29a3b7b5b9f5a..f78e931d5224a 100644 --- a/src/tests/Interop/COM/ComWrappers/API/Program.cs +++ b/src/tests/Interop/COM/ComWrappers/API/Program.cs @@ -101,7 +101,7 @@ static void ValidateComInterfaceCreation() // Allocate a wrapper for the object IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); - Assert.AreNotEqual(comWrapper, IntPtr.Zero); + Assert.AreNotEqual(IntPtr.Zero, comWrapper); // Get a wrapper for an object and verify it is the same one. IntPtr comWrapperMaybe = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); @@ -109,9 +109,9 @@ static void ValidateComInterfaceCreation() // Release the wrapper int count = Marshal.Release(comWrapper); - Assert.AreEqual(count, 1); + Assert.AreEqual(1, count); count = Marshal.Release(comWrapperMaybe); - Assert.AreEqual(count, 0); + Assert.AreEqual(0, count); // Create a new wrapper IntPtr comWrapperNew = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport); @@ -121,7 +121,27 @@ static void ValidateComInterfaceCreation() // Release the new wrapper count = Marshal.Release(comWrapperNew); - Assert.AreEqual(count, 0); + Assert.AreEqual(0, count); + } + + static void ValidateComInterfaceCreationRoundTrip() + { + Console.WriteLine($"Running {nameof(ValidateComInterfaceCreationRoundTrip)}..."); + + var testObj = new Test(); + + var wrappers = new TestComWrappers(); + + // Allocate a wrapper for the object + IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.None); + Assert.AreNotEqual(IntPtr.Zero, comWrapper); + + var testObjUnwrapped = wrappers.GetOrCreateObjectForComInstance(comWrapper, CreateObjectFlags.Unwrap); + Assert.AreEqual(testObj, testObjUnwrapped); + + // Release the wrapper + int count = Marshal.Release(comWrapper); + Assert.AreEqual(0, count); } static void ValidateFallbackQueryInterface() @@ -144,17 +164,17 @@ static void ValidateFallbackQueryInterface() var anyGuid = new Guid("1E42439C-DCB5-4701-ACBD-87FE92E785DE"); testObj.ICustomQueryInterface_GetInterfaceIID = anyGuid; int hr = Marshal.QueryInterface(comWrapper, ref anyGuid, out result); - Assert.AreEqual(hr, 0); - Assert.AreEqual(result, testObj.ICustomQueryInterface_GetInterfaceResult); + Assert.AreEqual(0, hr); + Assert.AreEqual(testObj.ICustomQueryInterface_GetInterfaceResult, result); var anyGuid2 = new Guid("7996D0F9-C8DD-4544-B708-0F75C6FF076F"); hr = Marshal.QueryInterface(comWrapper, ref anyGuid2, out result); const int E_NOINTERFACE = unchecked((int)0x80004002); - Assert.AreEqual(hr, E_NOINTERFACE); - Assert.AreEqual(result, IntPtr.Zero); + Assert.AreEqual(E_NOINTERFACE, hr); + Assert.AreEqual(IntPtr.Zero, result); int count = Marshal.Release(comWrapper); - Assert.AreEqual(count, 0); + Assert.AreEqual(0, count); } static void ValidateCreateObjectCachingScenario() @@ -234,7 +254,7 @@ static void ValidatePrecreatedExternalWrapper() var iid = typeof(ITrackerObject).GUID; IntPtr iTestComObject; int hr = Marshal.QueryInterface(trackerObjRaw, ref iid, out iTestComObject); - Assert.AreEqual(hr, 0); + Assert.AreEqual(0, hr); var nativeWrapper = new ITrackerObjectWrapper(iTestComObject); // Register wrapper, but supply the wrapper. @@ -261,6 +281,23 @@ static void ValidatePrecreatedExternalWrapper() }); } + static void ValidateSuppliedInnerNotAggregation() + { + Console.WriteLine($"Running {nameof(ValidateSuppliedInnerNotAggregation)}..."); + + var cw = new TestComWrappers(); + + // Attempt to register a non-zero instance with a non-zero inner value without + // indicating the scenario is aggregaion. + var invalidInstance = new IntPtr(1); + var invalidInner = new IntPtr(2); + Assert.Throws( + () => + { + cw.GetOrRegisterObjectForComInstance(invalidInstance, CreateObjectFlags.None, new object(), invalidInner); + }); + } + static void ValidateIUnknownImpls() => TestComWrappers.ValidateIUnknownImpls(); @@ -473,10 +510,12 @@ static int Main(string[] doNotUse) try { ValidateComInterfaceCreation(); + ValidateComInterfaceCreationRoundTrip(); ValidateFallbackQueryInterface(); ValidateCreateObjectCachingScenario(); ValidateWrappersInstanceIsolation(); ValidatePrecreatedExternalWrapper(); + ValidateSuppliedInnerNotAggregation(); ValidateIUnknownImpls(); ValidateBadComWrapperImpl(); ValidateRuntimeTrackerScenario(); diff --git a/src/tests/Interop/COM/ComWrappers/Common.cs b/src/tests/Interop/COM/ComWrappers/Common.cs index 083b4faa55d55..5d1994131e7a7 100644 --- a/src/tests/Interop/COM/ComWrappers/Common.cs +++ b/src/tests/Interop/COM/ComWrappers/Common.cs @@ -334,7 +334,7 @@ public unsafe static void Init( if (isAggregation) { // Indicate the scenario is aggregation - createObjectFlags |= (CreateObjectFlags)4; + createObjectFlags |= CreateObjectFlags.Aggregation; // The instance supports IReferenceTracker. if (classNative.ReferenceTracker != default(IntPtr)) diff --git a/src/tests/Regressions/coreclr/GitHub_43763/test43763.cs b/src/tests/Regressions/coreclr/GitHub_43763/test43763.cs index 7e8c63013379b..76f4e6aa8681c 100644 --- a/src/tests/Regressions/coreclr/GitHub_43763/test43763.cs +++ b/src/tests/Regressions/coreclr/GitHub_43763/test43763.cs @@ -1,4 +1,7 @@ -class Program +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +class Program { static int Main(string[] args) { diff --git a/src/tests/Regressions/coreclr/GitHub_45037/test45037.cs b/src/tests/Regressions/coreclr/GitHub_45037/test45037.cs index 61dfeedec558d..0d1ef19a28d4a 100644 --- a/src/tests/Regressions/coreclr/GitHub_45037/test45037.cs +++ b/src/tests/Regressions/coreclr/GitHub_45037/test45037.cs @@ -1,3 +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.Reflection; diff --git a/src/tests/Regressions/coreclr/GitHub_45082/test45082.cs b/src/tests/Regressions/coreclr/GitHub_45082/test45082.cs index e035fa3475f34..304720631506d 100644 --- a/src/tests/Regressions/coreclr/GitHub_45082/test45082.cs +++ b/src/tests/Regressions/coreclr/GitHub_45082/test45082.cs @@ -1,3 +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; diff --git a/src/tests/Regressions/coreclr/GitHub_47007/test47007.cs b/src/tests/Regressions/coreclr/GitHub_47007/test47007.cs new file mode 100644 index 0000000000000..4ec0849facbf9 --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_47007/test47007.cs @@ -0,0 +1,24 @@ +// 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; + +class Base +{ + public virtual Base Method() => null; +} +class Derived : Base +{ + public override Derived Method() => null; +} + +class Program +{ + static int Main() + { + _ = new Derived(); + + return 100; + } +} diff --git a/src/tests/Regressions/coreclr/GitHub_47007/test47007.csproj b/src/tests/Regressions/coreclr/GitHub_47007/test47007.csproj new file mode 100644 index 0000000000000..559507198cf9e --- /dev/null +++ b/src/tests/Regressions/coreclr/GitHub_47007/test47007.csproj @@ -0,0 +1,10 @@ + + + Exe + BuildAndRun + 1 + + + + + diff --git a/src/tests/profiler/native/profiler.h b/src/tests/profiler/native/profiler.h index 9231c576cd353..d3da750f3467c 100644 --- a/src/tests/profiler/native/profiler.h +++ b/src/tests/profiler/native/profiler.h @@ -68,14 +68,14 @@ class ShutdownGuard // ELT hooks will continue to be called. We would AV if we tried to call // in to freed resources. #define SHUTDOWNGUARD() \ - ShutdownGuard(); \ + ShutdownGuard shutdownGuard; \ if (ShutdownGuard::HasShutdownStarted()) \ { \ return S_OK; \ } #define SHUTDOWNGUARD_RETVOID() \ - ShutdownGuard(); \ + ShutdownGuard shutdownGuard; \ if (ShutdownGuard::HasShutdownStarted()) \ { \ return; \ diff --git a/src/tests/reflection/regression/manyStackArgs/manyStackArgs.cs b/src/tests/reflection/regression/manyStackArgs/manyStackArgs.cs new file mode 100644 index 0000000000000..11171e15c4088 --- /dev/null +++ b/src/tests/reflection/regression/manyStackArgs/manyStackArgs.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +public class TestSmallStackArgsClass +{ + public TestSmallStackArgsClass() + { } + + [MethodImpl(MethodImplOptions.NoInlining)] + public int TestSmallStackArgsMethod(short s1, short s2, short s3, short s4, short s5, short s6, short s7, short s8, short s9, short s10, short s11, short s12) + { + if (s1 != 1 || s2 != 2 || s3 != 3 || s4 != 4 || s5 != 5 || s6 != 6 || s7 != 7 || s8 != 8 || s9 != 9 || s10 != 10 || s11 != 11 || s12 != 12) + { + Console.WriteLine("small stack arguments do not match."); + Console.WriteLine("Expected: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12"); + Console.WriteLine("Actual: " + s1 + ", " + s2 + ", " + s3 + ", " + s4 + ", " + s5 + ", " + s6 + ", " + s7 + ", " + s8 + ", " + s9 + ", " + s10 + ", " + s11 + ", " + s12); + return 101; + } + return 100; + } +} + +public class TestMethodInfo +{ + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int InvokeReflection(object testClassObject, MethodInfo testMethod, object[] args) + { + + object retValue = testMethod.Invoke(testClassObject, args); + int r = Convert.ToInt32(retValue); + return r; + } + + + public static int Main() + { + Type testClass = Type.GetType("TestSmallStackArgsClass"); + ConstructorInfo testConstructor = testClass.GetConstructor(Type.EmptyTypes); + object testClassObject = testConstructor.Invoke(new object[] { }); + + MethodInfo testMethod = testClass.GetMethod("TestSmallStackArgsMethod"); + var args = new object[12]; + for (short i = 0; i < 12; ++i) + { + args[i] = (short)(i + 1); + } + + int r = InvokeReflection(testClassObject, testMethod, args); + + if (r != 100) + { + Console.WriteLine("The test failed"); + return 101; + } + Console.WriteLine("The test passed"); + return 100; + } +} diff --git a/src/tests/reflection/regression/manyStackArgs/manyStackArgs.csproj b/src/tests/reflection/regression/manyStackArgs/manyStackArgs.csproj new file mode 100644 index 0000000000000..adc3b7203ae20 --- /dev/null +++ b/src/tests/reflection/regression/manyStackArgs/manyStackArgs.csproj @@ -0,0 +1,13 @@ + + + Exe + 0 + + + None + True + + + + +