diff --git a/.azure-ci.yml b/.azure-ci.yml index fe836bdda2e0..de5c4e809703 100644 --- a/.azure-ci.yml +++ b/.azure-ci.yml @@ -69,7 +69,6 @@ jobs: - WindowsNoTest - AllConfigurations - LinuxTest - - LinuxNoTest - MacOS - RedHat6 # - FreeBSD diff --git a/.gitignore b/.gitignore index f680d2ef59fa..a6a208ee0737 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,9 @@ syntax: glob ### VisualStudio ### # Tool Runtime Dir -/[Tt]ools/ .dotnet/ .packages/ +.tools/ # User-specific files *.suo diff --git a/Directory.Build.props b/Directory.Build.props index 23ce8247235f..05f23e1bf042 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -31,19 +31,25 @@ - + Debug Release - + - OSX - FreeBSD - NetBSD - Linux + + OSX + FreeBSD + NetBSD + Linux $(OS) @@ -70,7 +76,6 @@ package - true @@ -96,14 +101,14 @@ - + $(_excludeRestorePackageImports) $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin')) - + $([MSBuild]::NormalizePath('$(ArtifactsBinDir)', 'native', '$(BuildConfiguration)')) @@ -150,8 +155,6 @@ https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json; https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json; - https://dotnet.myget.org/F/dotnet-buildtools/api/v3/index.json; - https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; https://api.nuget.org/v3/index.json; $(OverridePackageSource); $(RestoreSources) @@ -164,7 +167,7 @@ Library Open - true + true $(RepositoryEngineeringDir)DefaultGenApiDocIds.txt true @@ -196,7 +199,7 @@ <_portableOS Condition="'$(OSGroup)' == 'Unix' AND '$(_runtimeOSFamily)' != 'osx' AND '$(_runtimeOSFamily)' != 'FreeBSD' AND '$(_runtimeOS)' != 'linux-musl'">linux - <_packageRID/> + <_packageRID /> <_packageRID Condition="'$(PortableBuild)' == 'true'">$(_portableOS)-$(ArchGroup) <_packageRID Condition="$(TargetGroup.StartsWith('uap'))">win10-$(ArchGroup) <_packageRID Condition="$(TargetGroup.EndsWith('aot'))">$(_packageRID)-aot @@ -215,8 +218,7 @@ - true - true + true true true true @@ -236,7 +238,7 @@ - + $(CopyrightNetFoundation) - + @@ -253,7 +255,7 @@ portable - + false @@ -262,10 +264,15 @@ false - + - - true + + true + + optimization.windows_nt-x64.IBC.CoreFx + $(optimizationwindows_ntx64IBCCoreFxPackageVersion) + optimization.linux-x64.IBC.CoreFx + $(optimizationwindows_ntx64IBCCoreFxPackageVersion) @@ -317,11 +324,11 @@ true - + false true true - + $(NoWarn);BCL0020 @@ -353,7 +360,8 @@ $(RefRootPath)netstandard/ $(RefRootPath)netstandard2.1/ $(RefRootPath)netfx/ - $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'tools')) + + $([MSBuild]::NormalizeDirectory('$(RepoRoot)', '.tools', 'globaltools')) $([MSBuild]::NormalizeDirectory('$(ArtifactsDir)', 'ibc')) $([MSBuild]::NormalizeDirectory('$(ArtifactsToolsetDir)', 'ilasm')) $([MSBuild]::NormalizeDirectory('$(ArtifactsToolsetDir)', 'ILLink')) @@ -373,13 +381,11 @@ $(ArtifactsBinDir)pkg\netcoreapp\ref $(ArtifactsBinDir)pkg\netcoreapp\lib - $(ArtifactsBinDir)pkg\netcoreappaot\lib $(ArtifactsBinDir)pkg\uap\ref $(ArtifactsBinDir)pkg\uap\lib $(ArtifactsBinDir)pkg\uapaot\lib $(ArtifactsBinDir)pkg\netfx\ref $(ArtifactsBinDir)pkg\netfx\lib - $(ArtifactsBinDir)NetStandardTestSuite\ true @@ -452,7 +458,7 @@ - + false diff --git a/Directory.Build.targets b/Directory.Build.targets index 42bcbb6ae9ff..8591539bf844 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -68,18 +68,13 @@ ILLinkTrimAssembly=true - - $(NETCoreAppAotPackageRuntimePath) - - ILLinkTrimAssembly=true - - + $(UAPPackageRefPath) - + $(UAPPackageRuntimePath) - + $(UAPAOTPackageRuntimePath) @@ -88,25 +83,17 @@ $(NETCoreAppTestSharedFrameworkPath) - - $(ILCFXInputFolder) - - + $(ILCFXInputFolder) - + $(UAPTestSharedFrameworkPath) - + $(TestHostRootPath) - - - $(NETStandardTestSuiteOutputPath)$(AssemblyName)/ - - $(RefRootPath)netstandard/ @@ -127,6 +114,10 @@ Include="@(_TargetGroupsWithIsAot->WithMetadataValue('IsAot', 'false'))"> $(RefRootPath)%(Identity)/ + + + $(ArtifactsBinDir)runtime/%(Identity)-$(ConfigurationGroup)-$(ArchGroup) + diff --git a/Documentation/api-guidelines/nullability.md b/Documentation/api-guidelines/nullability.md new file mode 100644 index 000000000000..f48dbd0a65b0 --- /dev/null +++ b/Documentation/api-guidelines/nullability.md @@ -0,0 +1,92 @@ +# Nullability annotations + +C# 8 provides an opt-in feature that allows for the compiler to track reference type nullability in order to catch potential null dereferences. We are starting to adopt that feature in both System.Private.CoreLib in coreclr and in the libraries in corefx, working up from the bottom of the stack. We're doing this for three primary reasons, in order of importance: + +- **To annotate the .NET Core surface area with appropriate nullability annotations.** While this could be done solely in the reference assemblies, we're doing it first in the implementation to help validate the selected annotations. +- **To help validate the nullability feature itself.** With millions of lines of C# code, we have a very large and robust codebase with which to try out the feature and find areas in which it shines and areas in which we can improve it. +- **To find null-related bugs in .NET Core itself.** We expect to find relatively few meaningful bugs, due to how relatively well-tested the codebases are and how long they've been around. + +## Breaking Change Guidance + +We are striving to get annotations correct the "first time" and are doing due-diligence in an attempt to do so. However, we acknowledge that we are likely to need to augment and change some annotations in the future: + +- **Mistakes.** Given the sheer number of APIs being reviewed and annotated, we are likely to make some errors, and we'd like to be able to fix them so that long-term customers get the greatest benefit. +- **Breadth.** We are unlikely to have the time to annotate all of the enormous number of APIs in .NET for an initial release, and we'd like to be able to finish the task in subsequent updates. +- **Feedback.** We may need to revisit some "gray area" decisions as to whether a parameter or return type should be nullable or non-nullable (more details later). + +Any such additions or changes to annotations can impact the warnings consuming code receives if that code has opted in to nullability analysis and warnings. Even so, for at least the foreseeable future we may still do so. We will be very thoughtful about when and how we do. + +## Annotation Guidance + +Nullability annotations are considered to represent intent: they represent the nullability contract for the member. Any deviation from that intent on the part of an implementation should be considered an implementation bug, and the compiler will help to minimize the chances of such bugs via its flow analysis and nullability warnings. At the same time, it's important to recognize that the validation performed by the compiler isn't perfect; it can have both false positive warnings (suggesting that something may be null even when it isn't) and false negatives (not warning when something that may be null is dereferenced). The compiler cannot guarantee that an API declared as returning a non-nullable reference never returns null, just as it can't validate that an implementation declared as accepting nulls always behaves correctly when given them. When deciding how to annotate APIs, it's important then to consider the desired contract rather than the current implementation; in other words, prefer to first annotate the API surface area the way that's desired, and only then work to address any warnings in the codebase, rather than driving the API surface area annotations based on where those warnings lead. + +- **DO** annotate all new APIs with the desired contract. +- **CONSIDER** changing that contract if overwhelming use suggests a different de facto contract. This is particularly relevant to virtual/abstract/interface methods defined in a library where all implementations may not be under your control, and derived implementations may not have adhered to the original intent. +- **DO** continue to validate all arguments as you would have prior to nullability warnings. In particular, if you would have checked an argument for null and thrown an ArgumentNullException if it was null, continue to do so, even if the parameter is defined as non-nullable. +- **DO NOT** remove existing argument validation when annotating existing APIS. +- **AVOID** making any changes while annotating that impact the generated IL for an implementation (e.g. `some.Method()` to `some?.Method()`). Any such changes should be thoroughly analyzed and reviewed as a bug fix. + +The majority of reference type usage in our APIs is fairly clear as to whether it should be nullable or not. For parameters, these general guidelines cover the majority of cases: + +- **DO** define a parameter as non-nullable if the method checks for null and throws an `Argument{Null}Exception` if `null` is passed in for that parameter (whether explicitly in that same method or implicitly as part of some method it calls), such that there's no way null could be passed in and have the method return successfully. +- **DO** define a parameter as non-nullable if the method fails to check for `null` but instead will always end up dereferencing the `null` and throwing a `NullReferenceException`. +- **DO** define a parameter as nullable if the parameter is explicitly documented to accept `null`. +- **DO** define a parameter as nullable if method checks the parameter for `null` and does something other than throw. This may include normalizing the input, e.g. treating `null` as `string.Empty`. +- **DO** define a parameter as nullable if the parameter is optional and has a default value of `null`. +- **DO** prefer nullable over non-nullable if there's any disagreement between the previous guidelines. For example, if a method has documentation that suggests `null` isn't accepted but the implementation explicitly checks for, normalizes, and accepts a `null` input, the parameter should be defined nullable. + +However, there are some gray areas that require case-by-case analysis to determine intent. In particular, if a parameter isn't validated nor sanitized nor documented regarding null, but in some cases simply ignored such that a null doesn't currently cause any problems, several factors should be considered when determining whether to annotate it as null. +- Is null ever passed in our own code bases? If yes, it likely should be nullable. +- Is null ever passed in prominent 3rd-party code bases? If yes, it likely should be nullable. +- Is null likely to be interpreted as a default / nop placeholder by callers? If yes, it likely should be nullable. +- Is null accepted by other methods in a similar area or that have a similar purposes in the same code base? If yes, it likely should be nullable. +- If the method is largely oblivious to null and just happens to still work if null is passed, and if the API's purpose wouldn't make sense if null were used, the parameter likely should be non-nullable. + +Things are generally easier when looking at return values (and out parameters), as those can largely be driven by what the API's implementation is capable of: + +- **DO** define a return value or out parameter as nullable if it may be assigned `null` under any circumstance. +- **DO** define all other return values or out parameters as non-nullable. + +Annotating one of our return types as non-nullable is equivalent to documenting a guarantee that it will never return null. Violations of that guarantee are bugs to be fixed in the implementation. However, there is a huge gap here, in the form of overridable members… + +### Virtual/Abstract Methods and Interfaces + +For virtual members, annotating a return type as non-nullable places a requirement on all overrides to meet those same guarantees, just as any other documented behaviors of a virtuals apply to all overrides, whether those stated guarantees can be enforced by the compiler or not. An override that doesn't abide by these guarantees has a bug. For existing virtual APIs that have already documented a guarantee about a non-nullable return, it's expected that the return type will be annotated as non-nullable, and derived types must continue to respect that guarantee, albeit now with the compiler's assistance. + +However, for existing virtual APIs that do not have any such strong guarantee documented but where the intent was for the return value to be non-null, it is a grayer area. The most accurate return type would be T?, whereas the intent-based return type would be T. For T?, the pros are that it accurately reflects that nulls may emerge, but at the expense of consumers that know a null will never emerge having to use `!` or some other suppression when dereferencing. For T, the pros are that it accurately conveys the intent to overriders and allows consumers to avoid needing any form of suppression, but ironically at the expense of potential increases in occurrences of NullReferenceExceptions due to consumers then not validating the return type to be non-null and not being able to trust in the meaning of a method returning non-nullable. As such, there are several factors to then consider when deciding which return type to use for an existing virtual/abstract/interface method: +1. How common is it that an existing override written before the guarantee was put in effect would return null? +2. How widespread are overrides of the method in question? This contributes to (1). +3. How common is it to invoke the method via the base vs via a derived type that may narrow the return type to `T` from `T?`? +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 corefx, Roslyn, NuGet, ASP.NET, 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. + +As such, for now, we will start with `Object.ToString` returning `string?`. We can re-evaluate this decision as we get more experience with consumers of the feature. + +In contrast, for `Exception.Message` which is also virtual, we plan to have it be non-nullable, even though technically a derived class could override it to return null, because doing so is so rare that we couldn't find any meaningful examples of doing so. + +## Code Review Guidance + +Code reviews for enabling the nullability warnings are particularly interesting in that they often differ significantly from general code reviews. Typically, a code reviewer focuses only on the code actually being modified (e.g. the lines highlighted by the code diffing tool); however, enabling the nullability feature has a much broader impact, in that it effectively inverts the meaning of every reference type use in the codebase (or, more specifically, in the scope at which the nullability warning context was applied). So, for example, if you turn on nullability for a whole file (`#enable nullable` at the top of the file) and then touch no other lines in the file, every method that accepts a `string` is now accepting a non-nullable `string`; whereas previously passing in `null` to that argument would be fine, now the compiler will warn about it, and to allow nulls, the argument must be changed to `string?`. This means that enabling nullability checking requires reviewing all exposed APIs in that context, regardless of whether they were modified or not, as the contract exposed by the API may have been implicitly modified. + +A code review for enabling nullability generally involves three passes: + +- **Review all implementation changes made in the code.** Except when explicitly fixing a bug (which should be rare), the annotations employed for nullability should have zero impact on the generated IL (other than potentially some added attributes in the metadata). The most common changes are: + + - Adding `?` to reference type parameters and local symbols. These inform the compiler that nulls are allowed. For locals, they evaporate entirely at compile time. For parameters, they impact the [Nullable(...)] attributes emitted into the metadata by the compiler, but have no effect on the implementation IL. + + - Adding `!` to reference type usage. These essentially suppress the null warning, telling the compiler to treat the expression as if it's non-null. These evaporate at compile-time. + + - Adding `Debug.Assert(reference != null);` statements. These inform the compiler that the mentioned reference is non-null, which will cause the compiler to factor that in and have the effect of suppressing subsequent warnings on that reference (until the flow analysis suggests that could change). As with any Debug.Assert, these evaporate at compile-time in release builds (where DEBUG isn't defined). + + - Most any other changes have the potential to change the IL, which should not be necessary for the feature. In particular, it's common for `?`s on dereferences to sneak in, e.g. changing `someVar.SomeMethod()` to `someVar?.SomeMethod()`; that is a change to the IL, and should only be employed when there's an actual known bug that's important to fix, as otherwise we're incurring unnecessary cost. + + - Any `!`s added that should have been unnecessary and are required due to either a compiler issue or due to lack of expressibility about annotations should have a `// TODO-NULLABLE: http://link/to/relevant/issue` comment added on the same line. Issues due to lack of expressability should link to https://github.com/dotnet/roslyn/issues/26761. Issues due to lack of annotation support for never-returning methods should link to https://github.com/dotnet/csharplang/issues/538. + +- **Review the API changes explicitly made.** These are the ones that show up in the diff. They should be reviewed to validate that they make sense from a contract perspective. Do we expect/allow nulls everywhere a parameter reference type was augmented with `?`? Do we potentially return nulls everywhere a return type was augmented with `?`? Was anything else changed that could be an accidental breaking change (e.g. a value type parameter getting annotated to become a Nullable instead of a T)? Any APIs where the contract could be more constrained if more expressibility were present should have a `// TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761` comment. + +- **Review all other exported APIs (e.g. public and protected on public types) for all reference types in both return and parameter positions.** Anything that wasn't changed to be `?` is now defined as non-nullable. For parameters, that means consuming code will now get a harsh warning if it tries to pass null, and thus these should be changed if null actually is allowed / expected. For returns, it means the API will never return null; if it might return null in some circumstance, the API should be changed to return `?`. This is the most time consuming and tedious part of the review. diff --git a/Documentation/building/windows-instructions.md b/Documentation/building/windows-instructions.md index c367bede5511..d777addfcffc 100644 --- a/Documentation/building/windows-instructions.md +++ b/Documentation/building/windows-instructions.md @@ -4,7 +4,7 @@ Building CoreFX on Windows ## Required Software 1. **[Visual Studio 2017](https://www.visualstudio.com/downloads/)** or **[Visual Studio 2019](https://visualstudio.microsoft.com/vs/preview/)** (Community, Professional, Enterprise) with the latest update must be installed. The Community version is completely free. -2. **[.NET Core SDK](https://www.microsoft.com/net/download/windows)** >= v2.1.401 must be installed which will add the `dotnet` toolchain to your path. +2. **[.NET Core SDK](https://www.microsoft.com/net/download/windows)** >= v3.0.0-preview3 must be installed which will add the `dotnet` toolchain to your path. 3. **[CMake](https://cmake.org/)** must be installed from [the CMake download page](https://cmake.org/download/#latest) and added to your path. CMake 3.14 or later is required to build with VS 2019. ### Visual Studio 2019 diff --git a/Documentation/coding-guidelines/coding-style.md b/Documentation/coding-guidelines/coding-style.md index 606b44ee3d93..06b67ffeb445 100644 --- a/Documentation/coding-guidelines/coding-style.md +++ b/Documentation/coding-guidelines/coding-style.md @@ -31,9 +31,7 @@ The general rule we follow is "use Visual Studio defaults". 15. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor. 16. When using labels (for goto), indent the label one less than the current indentation. -We have provided a Visual Studio vssettings file (`corefx.vssettings`) at the root of the corefx repository, enabling C# auto-formatting conforming to the above guidelines. Note that rules 7 and 8 are not covered by the vssettings, since these are not rules currently supported by VS formatting. - -Additionally for auto-formatting in non-Visual Studio editors, an [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has also been provided at the root of the corefx repository. +An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the corefx repository, enabling C# auto-formatting conforming to the above guidelines. We also use the [.NET Codeformatter Tool](https://github.com/dotnet/codeformatter) to ensure the code base maintains a consistent style over time, the tool automatically fixes the code base to conform to the guidelines outlined above. diff --git a/Documentation/project-docs/issue-guide.md b/Documentation/project-docs/issue-guide.md index 730f51b526a0..4c99053dfc07 100644 --- a/Documentation/project-docs/issue-guide.md +++ b/Documentation/project-docs/issue-guide.md @@ -70,11 +70,11 @@ Areas are tracked by labels area-* (e.g. area-System.Collections). Each area | [System.Linq.Parallel](https://github.com/dotnet/corefx/labels/area-System.Linq.Parallel) | **[@tarekgh](https://github.com/tarekgh)**, [@kouvel](https://github.com/kouvel) | | | [System.Management](https://github.com/dotnet/corefx/labels/area-System.Management) | [@Anipik](https://github.com/Anipik) | WMI | | [System.Memory](https://github.com/dotnet/corefx/labels/area-System.Memory) | [@ahsonkhan](https://github.com/ahsonkhan) | | -| [System.Net](https://github.com/dotnet/corefx/labels/area-System.Net) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@caesar1995](https://github.com/caesar1995), [@rmkerr](https://github.com/rmkerr), [@karelz](https://github.com/karelz) | Included:
  • System.Uri
| -| [System.Net.Http](https://github.com/dotnet/corefx/labels/area-System.Net.Http) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@caesar1995](https://github.com/caesar1995), [@rmkerr](https://github.com/rmkerr), [@karelz](https://github.com/karelz) | | +| [System.Net](https://github.com/dotnet/corefx/labels/area-System.Net) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@karelz](https://github.com/karelz) | Included:
  • System.Uri - [@wtgodbe](https://github.com/wtgodbe)
| +| [System.Net.Http](https://github.com/dotnet/corefx/labels/area-System.Net.Http) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@karelz](https://github.com/karelz) | | | [System.Net.Http.SocketsHttpHandler](https://github.com/dotnet/corefx/labels/area-System.Net.Http.SocketsHttpHandler) | [@geoffkizer](https://github.com/geoffkizer), [@wfurt](https://github.com/wfurt), [@davidsh](https://github.com/davidsh), [@karelz](https://github.com/karelz) | | -| [System.Net.Security](https://github.com/dotnet/corefx/labels/area-System.Net.Security) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@caesar1995](https://github.com/caesar1995), [@rmkerr](https://github.com/rmkerr), [@karelz](https://github.com/karelz) | | -| [System.Net.Sockets](https://github.com/dotnet/corefx/labels/area-System.Net.Sockets) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@caesar1995](https://github.com/caesar1995), [@rmkerr](https://github.com/rmkerr), [@karelz](https://github.com/karelz) | | +| [System.Net.Security](https://github.com/dotnet/corefx/labels/area-System.Net.Security) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@karelz](https://github.com/karelz) | | +| [System.Net.Sockets](https://github.com/dotnet/corefx/labels/area-System.Net.Sockets) | [@davidsh](https://github.com/davidsh), [@wfurt](https://github.com/wfurt), [@karelz](https://github.com/karelz) | | | [System.Numerics](https://github.com/dotnet/corefx/labels/area-System.Numerics) | [@tannergooding](https://github.com/tannergooding), [@ViktorHofer](https://github.com/ViktorHofer) | | | [System.Numerics.Tensors](https://github.com/dotnet/corefx/labels/area-System.Numerics.Tensors) | [@tannergooding](https://github.com/tannergooding) | | | [System.Reflection](https://github.com/dotnet/corefx/labels/area-System.Reflection) | [@steveharter](https://github.com/steveharter), [@GrabYourPitchforks](https://github.com/GrabYourPitchforks) | | diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index db542ca24f27..b25636f506b8 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -307,3 +307,28 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +License notice for Json.NET +------------------------------- + +https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/eng/InternalTools.props b/eng/InternalTools.props index db0a13dcae15..b16d4b44460d 100644 --- a/eng/InternalTools.props +++ b/eng/InternalTools.props @@ -14,8 +14,8 @@ - - https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + https://dnceng.pkgs.visualstudio.com/_packaging/dotnet-internal/nuget/v3/index.json; $(RestoreSources); diff --git a/eng/Tools.props b/eng/Tools.props index 0285180199fa..6cd5fb9f7829 100644 --- a/eng/Tools.props +++ b/eng/Tools.props @@ -90,7 +90,8 @@ - $(ArtifactsDir)tools + + $([MSBuild]::NormalizePath('$(RepoRoot)', '.tools', 'globaltools')) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index edee493fcfed..392fa78eccba 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,100 +1,100 @@ - + https://github.com/dotnet/coreclr - f1440bb696e2eee957a30f37c6c735f51696edad + 0c1f6b9e3282f04d48b756afc8dbe60334ffd9b1 - + https://github.com/dotnet/coreclr - f1440bb696e2eee957a30f37c6c735f51696edad + 0c1f6b9e3282f04d48b756afc8dbe60334ffd9b1 - + https://github.com/dotnet/coreclr - f1440bb696e2eee957a30f37c6c735f51696edad + 0c1f6b9e3282f04d48b756afc8dbe60334ffd9b1 - + https://github.com/dotnet/core-setup - ccdc90de5f757c56e13bf5c095ee5d2aa8ca1aa4 + 25abcf43fe1da7a8bdad3d5053ebc01dc80ea450 - + https://github.com/dotnet/core-setup - ccdc90de5f757c56e13bf5c095ee5d2aa8ca1aa4 + 25abcf43fe1da7a8bdad3d5053ebc01dc80ea450 - + https://github.com/dotnet/core-setup - ccdc90de5f757c56e13bf5c095ee5d2aa8ca1aa4 + 25abcf43fe1da7a8bdad3d5053ebc01dc80ea450 - + https://github.com/dotnet/corefx - 35249a0072b41a89ce1542deacb2611c2393dec0 + 1f9b84a0804e868c7e0f37a3c10fbaf7c735ae14 - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/standard - 31a38c14c8a4d06ea59c67706fe4399c1f14368f + 25538d60f7f4c2c79cf098f2b808907d87b516a7 - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://github.com/dotnet/arcade - 36bc9d99630b4b544c6f09065dc37c00b4ca90a9 + 09e01af076175a6dbf19e442707722e751a7163b - + https://dev.azure.com/dnceng/internal/_git/dotnet-optimization 4f9d0ecf2b8151859fd2bd0734af32ea59258a3d diff --git a/eng/Versions.props b/eng/Versions.props index 82a2bf74be74..90e5a35fcdea 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,31 +23,31 @@
- 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19177.11 - 2.4.0-beta.19202.3 - 2.5.1-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 1.0.0-beta.19202.3 - 2.2.0-beta.19202.3 - 1.0.0-beta.19202.3 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 2.4.0-beta.19216.2 + 2.5.1-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 1.0.0-beta.19216.2 + 2.2.0-beta.19216.2 + 1.0.0-beta.19216.2 - 3.0.0-preview5-27606-01 - 3.0.0-preview5-27606-01 - 3.0.0-preview5-27606-01 + 3.0.0-preview5-27618-02 + 3.0.0-preview5-27618-02 + 3.0.0-preview5-27618-02 - 3.0.0-preview4-27527-73 - 3.0.0-preview4-27527-73 + 3.0.0-preview5-27617-73 + 3.0.0-preview5-27617-73 - 3.0.0-preview5.19205.9 + 3.0.0-preview5.19217.12 - 2.1.0-prerelease.19205.2 + 2.1.0-prerelease.19217.2 - 99.99.99-master-20190308.5 + 99.99.99-master-20190313.3 diff --git a/eng/codeOptimization.targets b/eng/codeOptimization.targets index c52fec409f7f..b49f8e52bec5 100644 --- a/eng/codeOptimization.targets +++ b/eng/codeOptimization.targets @@ -9,6 +9,10 @@ + + $(IbcOptimizationDataDir)$(LinuxCoreFxOptimizationDataPackageId)\ + $(IbcOptimizationDataDir)$(WindowsCoreFxOptimizationDataPackageId)\ + <_optimizationDataAssembly Include="$(IbcOptimizationDataDir)**\$(TargetFileName)" /> @@ -17,4 +21,4 @@ - \ No newline at end of file + diff --git a/eng/common/CheckSymbols.ps1 b/eng/common/CheckSymbols.ps1 new file mode 100644 index 000000000000..074b423245c8 --- /dev/null +++ b/eng/common/CheckSymbols.ps1 @@ -0,0 +1,134 @@ +param( + [Parameter(Mandatory=$true)][string] $InputPath, # Full path to directory where NuGet packages to be checked are stored + [Parameter(Mandatory=$true)][string] $ExtractPath, # Full path to directory where the packages will be extracted during validation + [Parameter(Mandatory=$true)][string] $SymbolToolPath # Full path to directory where dotnet symbol-tool was installed +) + +Add-Type -AssemblyName System.IO.Compression.FileSystem + +function FirstMatchingSymbolDescriptionOrDefault { + param( + [string] $FullPath, # Full path to the module that has to be checked + [string] $TargetServerParam # Parameter to pass to `Symbol Tool` indicating the server to lookup for symbols + ) + + $FileName = [System.IO.Path]::GetFileName($FullPath) + $Extension = [System.IO.Path]::GetExtension($FullPath) + + # Those below are potential symbol files that the `dotnet symbol` might + # return. Which one will be returned depend on the type of file we are + # checking and which type of file was uploaded. + + # The file itself is returned + $SymbolPath = $SymbolsPath + "\" + $FileName + + # PDB file for the module + $PdbPath = $SymbolPath.Replace($Extension, ".pdb") + + # PDB file for R2R module (created by crossgen) + $NGenPdb = $SymbolPath.Replace($Extension, ".ni.pdb") + + # DBG file for a .so library + $SODbg = $SymbolPath.Replace($Extension, ".so.dbg") + + # DWARF file for a .dylib + $DylibDwarf = $SymbolPath.Replace($Extension, ".dylib.dwarf") + + .\dotnet-symbol.exe --symbols --modules $TargetServerParam $FullPath -o $SymbolsPath -d | Out-Null + + if (Test-Path $PdbPath) { + return "PDB" + } + elseif (Test-Path $NGenPdb) { + return "NGen PDB" + } + elseif (Test-Path $SODbg) { + return "DBG for SO" + } + elseif (Test-Path $DylibDwarf) { + return "Dwarf for Dylib" + } + elseif (Test-Path $SymbolPath) { + return "Module" + } + else { + return $null + } +} + +function CountMissingSymbols { + param( + [string] $PackagePath # Path to a NuGet package + ) + + # Ensure input file exist + if (!(Test-Path $PackagePath)) { + throw "Input file does not exist: $PackagePath" + } + + # Extensions for which we'll look for symbols + $RelevantExtensions = @(".dll", ".exe", ".so", ".dylib") + + # How many files are missing symbol information + $MissingSymbols = 0 + + $PackageId = [System.IO.Path]::GetFileNameWithoutExtension($PackagePath) + $ExtractPath = $ExtractPath + $PackageId; + $SymbolsPath = $ExtractPath + $PackageId + ".Symbols"; + + [System.IO.Compression.ZipFile]::ExtractToDirectory($PackagePath, $ExtractPath) + + # Makes easier to reference `symbol tool` + Push-Location $SymbolToolPath + + Get-ChildItem -Recurse $ExtractPath | + Where-Object {$RelevantExtensions -contains $_.Extension} | + ForEach-Object { + Write-Host -NoNewLine "`t Checking file" $_.FullName "... " + + $SymbolsOnMSDL = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--microsoft-symbol-server" + $SymbolsOnSymWeb = FirstMatchingSymbolDescriptionOrDefault $_.FullName "--internal-server" + + if ($SymbolsOnMSDL -ne $null -and $SymbolsOnSymWeb -ne $null) { + Write-Host "Symbols found on MSDL (" $SymbolsOnMSDL ") and SymWeb (" $SymbolsOnSymWeb ")" + } + else { + $MissingSymbols++ + + if ($SymbolsOnMSDL -eq $null -and $SymbolsOnSymWeb -eq $null) { + Write-Host "No symbols found on MSDL or SymWeb!" + } + else { + if ($SymbolsOnMSDL -eq $null) { + Write-Host "No symbols found on MSDL!" + } + else { + Write-Host "No symbols found on SymWeb!" + } + } + } + } + + Pop-Location + + return $MissingSymbols +} + +function CheckSymbolsAvailable { + if (Test-Path $ExtractPath) { + Remove-Item -recurse $ExtractPath + } + + Get-ChildItem "$InputPath\*.nupkg" | + ForEach-Object { + $FileName = $_.Name + Write-Host "Validating $FileName " + $Status = CountMissingSymbols "$InputPath\$FileName" + + if ($Status -ne 0) { + Write-Error "Missing symbols for $Status modules in the package $FileName" + } + } +} + +CheckSymbolsAvailable diff --git a/eng/common/PublishToPackageFeed.proj b/eng/common/PublishToPackageFeed.proj index ccb81e8c3550..e17f72644e3e 100644 --- a/eng/common/PublishToPackageFeed.proj +++ b/eng/common/PublishToPackageFeed.proj @@ -1,11 +1,13 @@ - + + + netcoreapp2.1 @@ -41,6 +43,16 @@ https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json https://dotnetfeed.blob.core.windows.net/arcade-validation/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json + https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json + https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json + https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json + + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + 3650 + true + false + + + + + + + + + + + + + + + + + diff --git a/eng/common/SigningValidation.proj b/eng/common/SigningValidation.proj index 17e40d128773..7045fb6fb9d4 100644 --- a/eng/common/SigningValidation.proj +++ b/eng/common/SigningValidation.proj @@ -1,18 +1,20 @@ - + + - + - PackageBasePath : Directory containing all files that need to be validated. + - SignCheckVersion : Version of SignCheck package to be used. + - SignValidationExclusionList : ItemGroup containing exclusion list to be forwarded to SignCheck. + - EnableJarSigningCheck : Whether .jar files should be validated. + - EnableStrongNameCheck : Whether strong name check should be performed. + --> + netcoreapp2.1 diff --git a/eng/common/darc-init.ps1 b/eng/common/darc-init.ps1 index 29c443212b45..81ffd16779cb 100644 --- a/eng/common/darc-init.ps1 +++ b/eng/common/darc-init.ps1 @@ -19,7 +19,7 @@ function InstallDarcCli ($darcVersion) { # Until we can anonymously query the BAR API for the latest arcade-services # build applied to the PROD channel, this is hardcoded. if (-not $darcVersion) { - $darcVersion = '1.1.0-beta.19175.6' + $darcVersion = '1.1.0-beta.19205.4' } $arcadeServicesSource = 'https://dotnetfeed.blob.core.windows.net/dotnet-arcade/index.json' diff --git a/eng/common/darc-init.sh b/eng/common/darc-init.sh index cab6cd5bf9f3..bd7eb4639864 100755 --- a/eng/common/darc-init.sh +++ b/eng/common/darc-init.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash source="${BASH_SOURCE[0]}" -darcVersion="1.1.0-beta.19175.6" +darcVersion="1.1.0-beta.19205.4" while [[ $# > 0 ]]; do opt="$(echo "$1" | awk '{print tolower($0)}')" diff --git a/eng/common/internal/Tools.csproj b/eng/common/internal/Tools.csproj index 1a81ff906f6e..1a39a7ef3f67 100644 --- a/eng/common/internal/Tools.csproj +++ b/eng/common/internal/Tools.csproj @@ -12,8 +12,12 @@ - - https://devdiv.pkgs.visualstudio.com/_packaging/8f470c7e-ac49-4afe-a6ee-cf784e438b93/nuget/v3/index.json; + + + https://devdiv.pkgs.visualstudio.com/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json; + + + $(RestoreSources); https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json; diff --git a/eng/common/templates/phases/publish-build-assets.yml b/eng/common/templates/phases/publish-build-assets.yml index 211967debab5..a0a8074282aa 100644 --- a/eng/common/templates/phases/publish-build-assets.yml +++ b/eng/common/templates/phases/publish-build-assets.yml @@ -5,6 +5,7 @@ parameters: condition: succeeded() continueOnError: false runAsPublic: false + publishUsingPipelines: false phases: - phase: Asset_Registry_Publish displayName: Publish to Build Asset Registry @@ -36,6 +37,7 @@ phases: /p:ManifestsPath='$(Build.StagingDirectory)/Download/AssetManifests' /p:BuildAssetRegistryToken=$(MaestroAccessToken) /p:MaestroApiEndpoint=https://maestro-prod.westus2.cloudapp.azure.com + /p:PublishUsingPipelines=${{ parameters.publishUsingPipelines }} /p:Configuration=$(_BuildConfig) condition: ${{ parameters.condition }} continueOnError: ${{ parameters.continueOnError }} diff --git a/eng/configurations/targetgroups.props b/eng/configurations/targetgroups.props index bc9482979dea..320103337fdb 100644 --- a/eng/configurations/targetgroups.props +++ b/eng/configurations/targetgroups.props @@ -166,20 +166,6 @@ netcoreapp3.0 netstandard - - 3.0 - netcoreapp3.0 - aot - netcoreapp;netcoreapp2.1 - netstandard - - - 3.0 - netcoreapp3.0 - aot - netcoreapp;netcoreapp3.0aot - netstandard - net45 Microsoft.TargetingPack.NETFramework.v4.5 diff --git a/eng/dependencies.props b/eng/dependencies.props index b0531b4ac8f0..1a8151c6ee8b 100644 --- a/eng/dependencies.props +++ b/eng/dependencies.props @@ -9,8 +9,8 @@ These ref versions are pulled from https://github.com/dotnet/versions. --> - 1ec5055956dc17901d269b582a9af9fa82475387 - 1ec5055956dc17901d269b582a9af9fa82475387 + c86a1e0c3659a3046d43dc278e9711cd23002d97 + c86a1e0c3659a3046d43dc278e9711cd23002d97 8bd1ec5fac9f0eec34ff6b34b1d878b4359e02dd @@ -22,9 +22,9 @@ - beta-27527-00 - beta-27527-00 - 1.0.0-beta-27527-00 + beta-27612-00 + beta-27612-00 + 1.0.0-beta-27612-00 4.4.0 diff --git a/eng/internal/NuGet.config b/eng/internal/NuGet.config new file mode 100644 index 000000000000..f3a94002b19a --- /dev/null +++ b/eng/internal/NuGet.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/eng/pipelines/corefx-base.yml b/eng/pipelines/corefx-base.yml index ca9b656350f3..67a02a0391a3 100644 --- a/eng/pipelines/corefx-base.yml +++ b/eng/pipelines/corefx-base.yml @@ -74,11 +74,14 @@ jobs: - _msbuildCommonParameters: '' - _archiveTestsParameter: '' + - _finalFrameworkArg: -framework $(_framework) + + - ${{ if ne(job._jobFramework, '')}}: + - _finalFrameworkArg: ${{ job._jobFramework }} - ${{ if eq(parameters.isOfficialBuild, 'true') }}: - _msbuildCommonParameters: /p:OfficialBuildId=$(Build.BuildNumber) - _dotnetFeedUrl: https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - - group: DotNet-Internal-Tools-Feed - group: DotNet-Blob-Feed - ${{ if eq(job.submitToHelix, 'true') }}: @@ -86,7 +89,7 @@ jobs: - ${{ if eq(parameters.isOfficialBuild, 'true') }}: - group: DotNet-HelixApi-Access - - _args: -restore -build -configuration $(_BuildConfig) -ci -buildtests -arch $(_architecture) -framework $(_framework) $(_archiveTestsParameter) + - _args: -configuration $(_BuildConfig) -ci -arch $(_architecture) $(_finalFrameworkArg) $(_archiveTestsParameter) ${{ job.buildExtraArguments }} - _commonArguments: $(_args) # Windows variables @@ -124,30 +127,30 @@ jobs: - ${{ if ne(job.preBuildSteps[0], '') }}: - ${{ job.preBuildSteps }} + - script: $(_buildScript) -restore $(_commonArguments) + displayName: Restore Build Tools + - ${{ if eq(parameters.isOfficialBuild, 'true') }}: - - task: NuGetToolInstaller@0 - inputs: - versionSpec: '4.9.2' - condition: ne(variables['_skipRestoreInternalTools'], 'true') - - task: NuGetCommand@2 + - task: DotNetCoreCLI@2 displayName: Restore internal tools + condition: ne(variables['_skipRestoreInternalTools'], 'true') inputs: command: restore feedsToUse: config - restoreSolution: 'eng\common\internal\Tools.csproj' - nugetConfigPath: 'NuGet.config' + projects: 'eng/common/internal/Tools.csproj' + nugetConfigPath: 'eng/internal/NuGet.config' restoreDirectory: '$(Build.SourcesDirectory)\.packages' + verbosityRestore: 'normal' + externalFeedCredentials: 'dotnet-core-internal-tooling' env: - VSS_NUGET_EXTERNAL_FEED_ENDPOINTS: '{"endpointCredentials": [{"endpoint":"https://devdiv.pkgs.visualstudio.com/_packaging/8f470c7e-ac49-4afe-a6ee-cf784e438b93/nuget/v3/index.json","username":"dn-bot","password":"$(dn-bot-devdiv-nuget-feed-read)"}, - {"endpoint":"https://devdiv.pkgs.visualstudio.com/_packaging/97a41293-2972-4f48-8c0e-05493ae82010/nuget/v3/index.json","username":"dn-bot","password":"$(dn-bot-devdiv-nuget-feed-read)"}, - {"endpoint":"https://devdiv.pkgs.visualstudio.com/_packaging/VS/nuget/v3/index.json","username":"dn-bot","password":"$(dn-bot-devdiv-nuget-feed-read)"}]}' TargetGroup: $(_framework) - ${{ if eq(job.customBuildSteps[0], '') }}: - script: $(_buildScript) + -build + -buildtests $(_commonArguments) /p:OuterLoop=$(_outerloop) - ${{ job.buildExtraArguments }} $(_msbuildCommonParameters) displayName: Build Sources and Tests diff --git a/eng/pipelines/linux.yml b/eng/pipelines/linux.yml index 14938c4dcfa8..2b623afeb34c 100644 --- a/eng/pipelines/linux.yml +++ b/eng/pipelines/linux.yml @@ -86,6 +86,15 @@ jobs: _buildScriptPrefix: 'ROOTFS_DIR=/crossrootfs/arm ' _buildExtraArguments: -warnAsError false + musl_arm64_Release: + _BuildConfig: Release + _architecture: arm64 + _framework: netcoreapp + _helixQueues: $(alpineArm64Queues) + _dockerContainer: alpine_37_arm64_container + _buildScriptPrefix: 'ROOTFS_DIR=/crossrootfs/arm64 ' + _buildExtraArguments: -warnAsError false /p:BuildNativeCompiler=--clang5.0 /p:RuntimeOS=linux-musl + pool: name: Hosted Ubuntu 1604 @@ -104,28 +113,28 @@ jobs: - linuxDefaultQueues: Centos.7.Amd64.Open+RedHat.7.Amd64.Open+Debian.8.Amd64.Open+Ubuntu.1604.Amd64.Open+Ubuntu.1804.Amd64.Open+OpenSuse.42.Amd64.Open+\(Fedora.28.Amd64\)ubuntu.1604.amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-28-helix-45b1fa2-20190402012449 - linuxArm64Queues: \(Ubuntu.1604.Arm64\)Ubuntu.1604.Arm64.Docker.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-b049512-20190321153539 - alpineQueues: \(Alpine.38.Amd64\)ubuntu.1604.amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.8-helix-45b1fa2-20190327215821 - + - ${{ if eq(parameters.isOfficialBuild, 'true') }}: - linuxDefaultQueues: Centos.7.Amd64+RedHat.7.Amd64+Debian.8.Amd64+Debian.9.Amd64+Ubuntu.1604.Amd64+Ubuntu.1804.Amd64+Ubuntu.1810.Amd64+OpenSuse.42.Amd64+SLES.12.Amd64+SLES.15.Amd64+\(Fedora.28.Amd64\)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-28-helix-45b1fa2-20190402012449+\(Fedora.29.Amd64\)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-29-helix-c6dc5e6-20190402012449 - linuxArm64Queues: \(Ubuntu.1604.Arm64\)Ubuntu.1604.Arm64.Docker@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-16.04-helix-arm64v8-b049512-20190321153539 - linuxArmQueues: \(Debian.9.Arm32\)Ubuntu.1604.Arm32@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-9-helix-arm32v7-b049512-20190321153542 - alpineQueues: \(Alpine.38.Amd64\)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.8-helix-45b1fa2-20190327215821+\(Alpine.39.Amd64\)ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.9-helix-e4eaef4-20190228230637 + - alpineArm64Queues: \(Alpine.38.Arm64\)Ubuntu.1604.Arm64.Docker@mcr.microsoft.com/dotnet-buildtools/prereqs:alpine-3.8-helix-arm64v8-46e69dd-20190327215724 # Legs without helix testing - # There is no point of running legs without outerloop tests, when in an outerloop build. - - ${{ if eq(parameters.isOuterloopBuild, 'false') }}: + # Only run this leg in PRs. + - ${{ if and(eq(parameters.isOfficialBuild, 'false'), eq(parameters.isOuterloopBuild, 'false')) }}: - job: LinuxNoTest displayName: Linux strategy: matrix: - ${{ if eq(parameters.isOfficialBuild, 'false') }}: - arm_Release: - _BuildConfig: Release - _architecture: arm - _framework: netcoreapp - _buildExtraArguments: /p:RuntimeOS=ubuntu.16.04 -warnAsError false - _buildScriptPrefix: 'ROOTFS_DIR=/crossrootfs/arm ' - _dockerContainer: ubuntu_1604_arm_cross_container + arm_Release: + _BuildConfig: Release + _architecture: arm + _framework: netcoreapp + _buildExtraArguments: /p:RuntimeOS=ubuntu.16.04 -warnAsError false + _buildScriptPrefix: 'ROOTFS_DIR=/crossrootfs/arm ' + _dockerContainer: ubuntu_1604_arm_cross_container musl_arm64_Release: _BuildConfig: Release @@ -133,7 +142,7 @@ jobs: _framework: netcoreapp _dockerContainer: alpine_37_arm64_container _buildScriptPrefix: 'ROOTFS_DIR=/crossrootfs/arm64 ' - _buildExtraArguments: -warnAsError false /p:BuildNativeClang=--clang5.0 /p:RuntimeOS=linux-musl + _buildExtraArguments: -warnAsError false /p:BuildNativeCompiler=--clang5.0 /p:RuntimeOS=linux-musl pool: name: Hosted Ubuntu 1604 diff --git a/eng/pipelines/outerloop.yml b/eng/pipelines/outerloop.yml index 4a916f952fc6..e3222799f409 100644 --- a/eng/pipelines/outerloop.yml +++ b/eng/pipelines/outerloop.yml @@ -8,6 +8,9 @@ resources: - container: ubuntu_1604_arm64_cross_container image: microsoft/dotnet-buildtools-prereqs:ubuntu-16.04-cross-arm64-a3ae44b-20180315221921 + - container: alpine_36_container + image: microsoft/dotnet-buildtools-prereqs:alpine-3.6-WithNode-f4d3fe3-20181213005010 + jobs: # Windows outerloop legs - ${{ if endsWith(variables['Build.DefinitionName'], 'windows') }}: diff --git a/eng/pipelines/publish.yml b/eng/pipelines/publish.yml index fe505183bf0a..7813515ffac2 100644 --- a/eng/pipelines/publish.yml +++ b/eng/pipelines/publish.yml @@ -25,11 +25,9 @@ jobs: variables: - group: Publish-Build-Assets - group: DotNet-Blob-Feed - - group: DotNet-MyGet-Publish - group: DotNet-Versions-Publish - _dotnetFeedUrl: https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - _maestroApiEndpoint: https://maestro-prod.westus2.cloudapp.azure.com - - _mygetFeedUrl: https://dotnet.myget.org/F/dotnet-core/api/v2/package - _manifestsDir: ${{ parameters.artifactsDir }}/AssetManifests - _TeamName: DotNetCore - _SignType: real @@ -96,13 +94,6 @@ jobs: /p:Configuration=${{ parameters.buildConfiguration }} displayName: Publish to Build Assets Registry - # - script: powershell -ExecutionPolicy ByPass -NoProfile eng\common\msbuild.ps1 eng\publish.proj - # -warnaserror:0 -ci - # /t:NuGetPush - # /p:NuGetSource=$(_mygetFeedUrl) - # /p:NuGetApiKey=$(dotnet-myget-org-api-key) - # displayName: Push to myget.org - - script: powershell -ExecutionPolicy ByPass -NoProfile eng\common\msbuild.ps1 build.proj -warnaserror:0 -ci /t:UpdatePublishedVersions diff --git a/eng/pipelines/windows.yml b/eng/pipelines/windows.yml index bd591873307c..b165a2ae837e 100644 --- a/eng/pipelines/windows.yml +++ b/eng/pipelines/windows.yml @@ -166,6 +166,7 @@ jobs: name: Hosted VS2017 submitToHelix: true + buildExtraArguments: /p:RuntimeOS=win10 # azure pipelines reporter only supports xunit results based tests. enableAzurePipelinesReporter: false @@ -176,9 +177,9 @@ jobs: - ${{ if eq(parameters.isOfficialBuild, 'true') }}: - allConfigurationsQueues: Windows.10.Amd64.ClientRS5 + _jobFramework: -allConfigurations customBuildSteps: - script: build.cmd - -restore -build -configuration $(_BuildConfig) -ci @@ -187,19 +188,8 @@ jobs: -arch $(_architecture) /p:RuntimeOS=win10 /p:ArchiveTests=Packages - $(_windowsOfficialBuildArguments) $(_msbuildCommonParameters) displayName: Build Packages and Tests - - ${{ if eq(parameters.isOfficialBuild, 'false') }}: - - script: build.cmd - -restore - -build - -configuration $(_BuildConfig) - -ci - -buildtests - -framework netstandard - -arch $(_architecture) - displayName: Build Netstandard Test Suite # TODO: UAPAOT official builds should send to helix using continuation runner. # Legs without HELIX testing diff --git a/eng/referenceFromRuntime.targets b/eng/referenceFromRuntime.targets index e739943c31e8..c81a64c967cb 100644 --- a/eng/referenceFromRuntime.targets +++ b/eng/referenceFromRuntime.targets @@ -48,7 +48,9 @@ <_filteredReferencePathFromRuntimeByFileName Include="@(_referencePathFromRuntimeByFileName)" - Condition="'@(_referencePathFromRuntimeByFileName)' == '@(ReferenceFromRuntime)' AND '%(Identity)' != ''" /> + Condition="'@(_referencePathFromRuntimeByFileName)' == '@(ReferenceFromRuntime)' AND '%(Identity)' != ''"> + @(ReferenceFromRuntime->'%(Aliases)') + <_remainingReferenceFromRuntime Include="@(ReferenceFromRuntime)" Exclude="@(_filteredReferencePathFromRuntimeByFileName)" /> @@ -58,12 +60,16 @@ <_filteredReferencePathFromRuntimeByFileName Include="@(_referencePathFromRuntimeByFileName)" - Condition="'@(_referencePathFromRuntimeByFileName)' == '@(_remainingReferenceFromRuntimeWithNI)' AND '%(Identity)' != ''" /> + Condition="'@(_referencePathFromRuntimeByFileName)' == '@(_remainingReferenceFromRuntimeWithNI)' AND '%(Identity)' != ''"> + @(_remainingReferenceFromRuntimeWithNI->'%(Aliases)') + <_missingReferenceFromRuntime Include="@(_remainingReferenceFromRuntimeWithNI)" Exclude="@(_filteredReferencePathFromRuntimeByFileName)" /> + + test/functional/ilc/ - + set DOTNET_CLI_TELEMETRY_OPTOUT=1;set DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1;set DOTNET_MULTILEVEL_LOOKUP=0 %HELIX_CORRELATION_PAYLOAD%\tools\dotnet.exe msbuild %HELIX_CORRELATION_PAYLOAD%\test.msbuild @@ -66,17 +66,24 @@ $(HelixCommand) /p:LocalPackagesPath="%HELIX_CORRELATION_PAYLOAD%\packages\ + + + true + - + - call RunTests.cmd %HELIX_CORRELATION_PAYLOAD% %HELIX_CORRELATION_PAYLOAD% %HELIX_CORRELATION_PAYLOAD%\tools - ./RunTests.sh $HELIX_CORRELATION_PAYLOAD $HELIX_CORRELATION_PAYLOAD $HELIX_CORRELATION_PAYLOAD/tools + call RunTests.cmd --runtime-path %HELIX_CORRELATION_PAYLOAD% --dotnet-root %HELIX_CORRELATION_PAYLOAD% + $(HelixCommand) --global-tools-dir "%HELIX_CORRELATION_PAYLOAD%\tools" + + + + ./RunTests.sh --runtime-path "$HELIX_CORRELATION_PAYLOAD" --dotnet-root "$HELIX_CORRELATION_PAYLOAD" + $(HelixCommand) --global-tools-dir "$HELIX_CORRELATION_PAYLOAD/tools" - - true - - diff --git a/external/optimizationData/optimizationData.depproj b/external/optimizationData/optimizationData.depproj index 7a5efe42cccc..7bdc2518c2c3 100644 --- a/external/optimizationData/optimizationData.depproj +++ b/external/optimizationData/optimizationData.depproj @@ -3,14 +3,11 @@ $(IbcOptimizationDataDir) false - - optimization.windows_nt-x64.IBC.CoreFx - optimization.linux-x64.IBC.CoreFx - $(optimizationwindows_ntx64IBCCoreFxPackageVersion) - + + diff --git a/external/runtime/Configurations.props b/external/runtime/Configurations.props index a63f4d4d76df..dd6a33c689e3 100644 --- a/external/runtime/Configurations.props +++ b/external/runtime/Configurations.props @@ -2,8 +2,6 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap10.0.16299; uap10.0.16299aot; diff --git a/external/runtime/runtime.depproj b/external/runtime/runtime.depproj index 83dad8708613..3a876a60aaf1 100644 --- a/external/runtime/runtime.depproj +++ b/external/runtime/runtime.depproj @@ -19,7 +19,7 @@ - 1.1.0-$(ProjectNTfsExpectedPrerelease) + 1.1.0-$(ProjectNTfsExpectedPrerelease) 1.1.0-rel-25728-00 diff --git a/external/test-runtime/Configurations.props b/external/test-runtime/Configurations.props index d9c88a0605aa..9fc9521a1890 100644 --- a/external/test-runtime/Configurations.props +++ b/external/test-runtime/Configurations.props @@ -3,7 +3,6 @@ uap; uapaot; - netcoreappaot; netstandard; netfx; netcoreapp; diff --git a/external/test-runtime/XUnit.Runtime.depproj b/external/test-runtime/XUnit.Runtime.depproj index c7faf26e0db6..e795c082bc1c 100644 --- a/external/test-runtime/XUnit.Runtime.depproj +++ b/external/test-runtime/XUnit.Runtime.depproj @@ -44,14 +44,14 @@ - - - - - - - - + + + + + + + + diff --git a/external/tools/Configurations.props b/external/tools/Configurations.props index 93d8aff55d97..2870dced4592 100644 --- a/external/tools/Configurations.props +++ b/external/tools/Configurations.props @@ -1,16 +1,7 @@  - netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; - netcoreapp-Unix; - netfx; - uap10.0.16299; - uap10.0.16299aot; - uap; - uapaot; - mono; + netstandard; diff --git a/global.json b/global.json index 3d75bc4ee04b..157d39d6d579 100644 --- a/global.json +++ b/global.json @@ -1,10 +1,10 @@ { "tools": { - "dotnet": "2.2.103" + "dotnet": "3.0.100-preview3-010431" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19202.3", - "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19202.3", - "Microsoft.NET.Sdk.IL": "3.0.0-preview4-27527-73" + "Microsoft.DotNet.Arcade.Sdk": "1.0.0-beta.19216.2", + "Microsoft.DotNet.Helix.Sdk": "2.0.0-beta.19216.2", + "Microsoft.NET.Sdk.IL": "3.0.0-preview5-27617-73" } } diff --git a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Configurations.props b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Configurations.props index eb733ee25372..19236e72c58f 100644 --- a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Configurations.props +++ b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Configurations.props @@ -2,7 +2,6 @@ netcoreapp; - netcoreappaot; diff --git a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj index ca5a4bb4515c..783584839dfc 100644 --- a/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj +++ b/pkg/Microsoft.Private.CoreFx.NETCoreApp/Microsoft.Private.CoreFx.NETCoreApp.pkgproj @@ -8,7 +8,6 @@ $(NETCoreAppPackageRefPath) $(NETCoreAppPackageRuntimePath) - $(NETCoreAppAotPackageRuntimePath) true diff --git a/pkg/Microsoft.Private.CoreFx.NETCoreApp/netcoreapp.rids.props b/pkg/Microsoft.Private.CoreFx.NETCoreApp/netcoreapp.rids.props index f0c090b90870..7ce2ea9e2c70 100644 --- a/pkg/Microsoft.Private.CoreFx.NETCoreApp/netcoreapp.rids.props +++ b/pkg/Microsoft.Private.CoreFx.NETCoreApp/netcoreapp.rids.props @@ -24,7 +24,6 @@ x86 - + false true diff --git a/pkg/test/frameworkSettings/netcoreapp3.0/settings.targets b/pkg/test/frameworkSettings/netcoreapp3.0/settings.targets index 988f2060f715..afde20c57942 100644 --- a/pkg/test/frameworkSettings/netcoreapp3.0/settings.targets +++ b/pkg/test/frameworkSettings/netcoreapp3.0/settings.targets @@ -4,19 +4,22 @@ 3.0 $(MicrosoftNETCoreAppPackageVersion) + true + + + + + + + + + + - - - - - - - - diff --git a/pkg/test/frameworkSettings/netstandard2.1/settings.targets b/pkg/test/frameworkSettings/netstandard2.1/settings.targets new file mode 100644 index 000000000000..815786db5c51 --- /dev/null +++ b/pkg/test/frameworkSettings/netstandard2.1/settings.targets @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.ActivityControl.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.ActivityControl.cs new file mode 100644 index 000000000000..34df748d39ab --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.ActivityControl.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + internal enum ActivityControl : uint + { + EVENT_ACTIVITY_CTRL_GET_ID = 1, + EVENT_ACTIVITY_CTRL_SET_ID = 2, + EVENT_ACTIVITY_CTRL_CREATE_ID = 3, + EVENT_ACTIVITY_CTRL_GET_SET_ID = 4, + EVENT_ACTIVITY_CTRL_CREATE_SET_ID = 5 + } + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EVENT_INFO_CLASS.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EVENT_INFO_CLASS.cs new file mode 100644 index 000000000000..a122002e9abe --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EVENT_INFO_CLASS.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + internal enum EVENT_INFO_CLASS + { + BinaryTrackInfo, + SetEnableAllKeywords, + SetTraits, + } + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EtwEnableCallback.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EtwEnableCallback.cs new file mode 100644 index 000000000000..6bb157520f35 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EtwEnableCallback.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + internal const int EVENT_CONTROL_CODE_DISABLE_PROVIDER = 0; + internal const int EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1; + internal const int EVENT_CONTROL_CODE_CAPTURE_STATE = 2; + + [StructLayout(LayoutKind.Sequential)] + internal struct EVENT_FILTER_DESCRIPTOR + { + public long Ptr; + public int Size; + public int Type; + } + + internal unsafe delegate void EtwEnableCallback( + in Guid sourceId, + int isEnabled, + byte level, + long matchAnyKeywords, + long matchAllKeywords, + EVENT_FILTER_DESCRIPTOR* filterData, + void* callbackContext); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventActivityIdControl.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventActivityIdControl.cs new file mode 100644 index 000000000000..886ff37d19fe --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventActivityIdControl.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, ExactSpelling = true)] + internal static extern int EventActivityIdControl(ActivityControl ControlCode, ref Guid ActivityId); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventRegister.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventRegister.cs new file mode 100644 index 000000000000..f5d245ec5d68 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventRegister.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, ExactSpelling = true)] + internal static extern unsafe uint EventRegister( + in Guid providerId, + EtwEnableCallback enableCallback, + void* callbackContext, + ref long registrationHandle); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventSetInformation.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventSetInformation.cs new file mode 100644 index 000000000000..381cb661c37e --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventSetInformation.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, ExactSpelling = true)] + internal static unsafe extern int EventSetInformation( + long registrationHandle, + EVENT_INFO_CLASS informationClass, + void* eventInformation, + int informationLength); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventTraceGuidsEx.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventTraceGuidsEx.cs new file mode 100644 index 000000000000..c5f6f3b18790 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventTraceGuidsEx.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + internal enum TRACE_QUERY_INFO_CLASS + { + TraceGuidQueryList, + TraceGuidQueryInfo, + TraceGuidQueryProcess, + TraceStackTracingInfo, + MaxTraceSetInfoClass + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TRACE_GUID_INFO + { + public int InstanceCount; + public int Reserved; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TRACE_PROVIDER_INSTANCE_INFO + { + public int NextOffset; + public int EnableCount; + public int Pid; + public int Flags; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct TRACE_ENABLE_INFO + { + public int IsEnabled; + public byte Level; + public byte Reserved1; + public ushort LoggerId; + public int EnableProperty; + public int Reserved2; + public long MatchAnyKeyword; + public long MatchAllKeyword; + } + + [DllImport(Interop.Libraries.Advapi32, ExactSpelling = true)] + internal static unsafe extern int EnumerateTraceGuidsEx( + TRACE_QUERY_INFO_CLASS TraceQueryInfoClass, + void* InBuffer, + int InBufferSize, + void* OutBuffer, + int OutBufferSize, + out int ReturnLength); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventUnregister.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventUnregister.cs new file mode 100644 index 000000000000..f387b3a9ab5c --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventUnregister.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, ExactSpelling = true)] + internal static extern uint EventUnregister(long registrationHandle); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteString.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteString.cs new file mode 100644 index 000000000000..a00a2f3a2c99 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteString.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Advapi32 + { + [DllImport(Libraries.Advapi32, ExactSpelling = true)] + internal static extern int EventWriteString( + long registrationHandle, + byte level, + long keyword, + string msg); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteTransfer.cs b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteTransfer.cs new file mode 100644 index 000000000000..2d3f45e839be --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Advapi32/Interop.EventWriteTransfer.cs @@ -0,0 +1,49 @@ +// 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.Runtime.InteropServices; +#if ES_BUILD_STANDALONE +using Microsoft.Diagnostics.Tracing; +#else +using System.Diagnostics.Tracing; +#endif + +internal partial class Interop +{ + internal partial class Advapi32 + { + /// + /// Call the ETW native API EventWriteTransfer and checks for invalid argument error. + /// The implementation of EventWriteTransfer on some older OSes (Windows 2008) does not accept null relatedActivityId. + /// So, for these cases we will retry the call with an empty Guid. + /// + internal static unsafe int EventWriteTransfer( + long registrationHandle, + in EventDescriptor eventDescriptor, + Guid* activityId, + Guid* relatedActivityId, + int userDataCount, + EventProvider.EventData* userData) + { + int HResult = EventWriteTransfer_PInvoke(registrationHandle, in eventDescriptor, activityId, relatedActivityId, userDataCount, userData); + if (HResult == Errors.ERROR_INVALID_PARAMETER && relatedActivityId == null) + { + Guid emptyGuid = Guid.Empty; + HResult = EventWriteTransfer_PInvoke(registrationHandle, in eventDescriptor, activityId, &emptyGuid, userDataCount, userData); + } + + return HResult; + } + + [DllImport(Interop.Libraries.Advapi32, ExactSpelling = true, EntryPoint = "EventWriteTransfer")] + private static unsafe extern int EventWriteTransfer_PInvoke( + long registrationHandle, + in EventDescriptor eventDescriptor, + Guid* activityId, + Guid* relatedActivityId, + int userDataCount, + EventProvider.EventData* userData); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Interop.Errors.cs b/src/Common/src/CoreLib/Interop/Windows/Interop.Errors.cs index 1a95db3c939b..2e4683dfd70a 100644 --- a/src/Common/src/CoreLib/Interop/Windows/Interop.Errors.cs +++ b/src/Common/src/CoreLib/Interop/Windows/Interop.Errors.cs @@ -18,6 +18,7 @@ internal partial class Errors internal const int ERROR_NOT_READY = 0x15; internal const int ERROR_SHARING_VIOLATION = 0x20; internal const int ERROR_HANDLE_EOF = 0x26; + internal const int ERROR_NOT_SUPPORTED = 0x32; internal const int ERROR_FILE_EXISTS = 0x50; internal const int ERROR_INVALID_PARAMETER = 0x57; internal const int ERROR_BROKEN_PIPE = 0x6D; diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.cs new file mode 100644 index 000000000000..067ee715870c --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.FileTimeToSystemTime.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static unsafe extern Interop.BOOL FileTimeToSystemTime(long* lpFileTime, Interop.Kernel32.SYSTEMTIME* lpSystemTime); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetProcessInformation.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetProcessInformation.cs new file mode 100644 index 000000000000..22e056937548 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetProcessInformation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + internal const int ProcessLeapSecondInfo = 8; + + internal struct PROCESS_LEAP_SECOND_INFO + { + public uint Flags; + public uint Reserved; + } + + [DllImport(Libraries.Kernel32)] + internal static unsafe extern Interop.BOOL GetProcessInformation(IntPtr hProcess, int ProcessInformationClass, void* ProcessInformation, int ProcessInformationSize); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTime.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTime.cs new file mode 100644 index 000000000000..710db5e4b99f --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTime.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static unsafe extern void GetSystemTime(Interop.Kernel32.SYSTEMTIME* lpSystemTime); + } +} diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.GetCurrentThreadId.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimeAsFileTime.cs similarity index 56% rename from src/Common/src/Interop/Windows/Kernel32/Interop.GetCurrentThreadId.cs rename to src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimeAsFileTime.cs index a92936e291b0..e2dcd906c2ad 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.GetCurrentThreadId.cs +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimeAsFileTime.cs @@ -5,11 +5,11 @@ using System; using System.Runtime.InteropServices; -internal static partial class Interop +internal partial class Interop { - internal static partial class Kernel32 + internal partial class Kernel32 { - [DllImport(Interop.Libraries.Kernel32, ExactSpelling = true)] - public static extern int GetCurrentThreadId(); + [DllImport(Libraries.Kernel32)] + internal static unsafe extern void GetSystemTimeAsFileTime(long* lpSystemTimeAsFileTime); } } diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimePreciseAsFileTime.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimePreciseAsFileTime.cs new file mode 100644 index 000000000000..e3262799d132 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.GetSystemTimePreciseAsFileTime.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static unsafe extern void GetSystemTimePreciseAsFileTime(long* lpSystemTimeAsFileTime); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs new file mode 100644 index 000000000000..43db7b47164d --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.SystemTimeToFileTime.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static unsafe extern Interop.BOOL SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* lpSystemTime, long* lpFileTime); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs new file mode 100644 index 000000000000..2cca7faed732 --- /dev/null +++ b/src/Common/src/CoreLib/Interop/Windows/Kernel32/Interop.TzSpecificLocalTimeToSystemTime.cs @@ -0,0 +1,18 @@ +// 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.Runtime.InteropServices; + +internal partial class Interop +{ + internal partial class Kernel32 + { + [DllImport(Libraries.Kernel32)] + internal static unsafe extern Interop.BOOL TzSpecificLocalTimeToSystemTime( + IntPtr lpTimeZoneInformation, + Interop.Kernel32.SYSTEMTIME* lpLocalTime, + Interop.Kernel32.SYSTEMTIME* lpUniversalTime); + } +} diff --git a/src/Common/src/CoreLib/Interop/Windows/NtDll/NtQueryInformationFile.cs b/src/Common/src/CoreLib/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs similarity index 100% rename from src/Common/src/CoreLib/Interop/Windows/NtDll/NtQueryInformationFile.cs rename to src/Common/src/CoreLib/Interop/Windows/NtDll/Interop.NtQueryInformationFile.cs diff --git a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems index 69fb5565a438..e51a210baea6 100644 --- a/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems +++ b/src/Common/src/CoreLib/System.Private.CoreLib.Shared.projitems @@ -177,6 +177,7 @@ + @@ -232,6 +233,7 @@ + @@ -421,6 +423,7 @@ + @@ -766,6 +769,7 @@ + @@ -797,13 +801,17 @@ - + + + + + @@ -906,8 +914,11 @@ + + + - + @@ -986,6 +997,13 @@ + + + + + + + @@ -998,6 +1016,7 @@ + @@ -1016,6 +1035,9 @@ + + + @@ -1031,6 +1053,7 @@ + @@ -1040,8 +1063,10 @@ + + @@ -1049,7 +1074,6 @@ - @@ -1057,17 +1081,14 @@ + - - - - @@ -1083,6 +1104,7 @@ + @@ -1114,20 +1136,30 @@ - + + + + + + + + + + + diff --git a/src/Common/src/CoreLib/System/Activator.RuntimeType.cs b/src/Common/src/CoreLib/System/Activator.RuntimeType.cs index f328b9c25114..270aa6ad65cf 100644 --- a/src/Common/src/CoreLib/System/Activator.RuntimeType.cs +++ b/src/Common/src/CoreLib/System/Activator.RuntimeType.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Globalization; +using System.Runtime.Loader; using System.Runtime.Remoting; using System.Threading; @@ -126,7 +127,7 @@ private static ObjectHandle CreateInstanceInternal(string assemblyString, { // Classic managed type assembly = RuntimeAssembly.InternalLoadAssemblyName( - assemblyName, ref stackMark); + assemblyName, ref stackMark, AssemblyLoadContext.CurrentContextualReflectionContext); } } diff --git a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs index fdf79985ee12..4df370337d0a 100644 --- a/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs +++ b/src/Common/src/CoreLib/System/Buffers/Text/Utf8Parser/Utf8Parser.Date.Default.cs @@ -83,12 +83,6 @@ private static bool TryParseDateTimeOffsetDefault(ReadOnlySpan source, out offsetMinutes = (int)(digit1 * 10 + digit2); } - TimeSpan offset = new TimeSpan(hours: offsetHours, minutes: offsetMinutes, seconds: 0); - if (sign == Utf8Constants.Minus) - { - offset = -offset; - } - if (!TryCreateDateTimeOffset(dateTime: dateTime, offsetNegative: sign == Utf8Constants.Minus, offsetHours: offsetHours, offsetMinutes: offsetMinutes, out value)) { bytesConsumed = 0; diff --git a/src/Common/src/CoreLib/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/Common/src/CoreLib/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs index 5c5fd994edc0..59e109a0431c 100644 --- a/src/Common/src/CoreLib/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs +++ b/src/Common/src/CoreLib/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs @@ -301,12 +301,12 @@ private static MemoryPressure GetMemoryPressure() const double HighPressureThreshold = .90; // Percent of GC memory pressure threshold we consider "high" const double MediumPressureThreshold = .70; // Percent of GC memory pressure threshold we consider "medium" - GC.GetMemoryInfo(out uint threshold, out _, out uint lastLoad, out _, out _); - if (lastLoad >= threshold * HighPressureThreshold) + GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); + if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * HighPressureThreshold) { return MemoryPressure.High; } - else if (lastLoad >= threshold * MediumPressureThreshold) + else if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * MediumPressureThreshold) { return MemoryPressure.Medium; } diff --git a/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs b/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs index dbb0bdf56608..0a36cb5f7681 100644 --- a/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs +++ b/src/Common/src/CoreLib/System/Collections/Generic/Dictionary.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.Serialization; -using System.Threading; namespace System.Collections.Generic { @@ -38,8 +37,11 @@ public class Dictionary : IDictionary, IDictionary, { private struct Entry { - public int hashCode; // Lower 31 bits of hash code, -1 if unused - public int next; // Index of next entry, -1 if last + // 0-based index of next entry in chain: -1 means end of chain + // also encodes whether this entry _itself_ is part of the free list by changing sign and subtracting 3, + // so -2 means end of free list, -3 means index 0 but on free list, -4 means index 1 but on free list, etc. + public int next; + public uint hashCode; public TKey key; // Key of entry public TValue value; // Value of entry } @@ -53,6 +55,7 @@ private struct Entry private IEqualityComparer _comparer; private KeyCollection _keys; private ValueCollection _values; + private const int StartOfFreeList = -3; // constants for serialization private const string VersionName = "Version"; // Do not rename (binary serialization) @@ -103,7 +106,7 @@ public Dictionary(IDictionary dictionary, IEqualityComparer Entry[] entries = d._entries; for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { Add(entries[i].key, entries[i].value); } @@ -278,7 +281,7 @@ public bool ContainsValue(TValue value) { for (int i = 0; i < _count; i++) { - if (entries[i].hashCode >= 0 && entries[i].value == null) return true; + if (entries[i].next >= -1 && entries[i].value == null) return true; } } else @@ -288,7 +291,7 @@ public bool ContainsValue(TValue value) // ValueType: Devirtualize with EqualityComparer.Default intrinsic for (int i = 0; i < _count; i++) { - if (entries[i].hashCode >= 0 && EqualityComparer.Default.Equals(entries[i].value, value)) return true; + if (entries[i].next >= -1 && EqualityComparer.Default.Equals(entries[i].value, value)) return true; } } else @@ -299,7 +302,7 @@ public bool ContainsValue(TValue value) EqualityComparer defaultComparer = EqualityComparer.Default; for (int i = 0; i < _count; i++) { - if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true; + if (entries[i].next >= -1 && defaultComparer.Equals(entries[i].value, value)) return true; } } } @@ -327,7 +330,7 @@ private void CopyTo(KeyValuePair[] array, int index) Entry[] entries = _entries; for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { array[index++] = new KeyValuePair(entries[i].key, entries[i].value); } @@ -375,9 +378,9 @@ private int FindEntry(TKey key) IEqualityComparer comparer = _comparer; if (comparer == null) { - int hashCode = key.GetHashCode() & 0x7FFFFFFF; + uint hashCode = (uint)key.GetHashCode(); // Value in _buckets is 1-based - i = buckets[hashCode % buckets.Length] - 1; + i = buckets[hashCode % (uint)buckets.Length] - 1; if (default(TKey) != null) { // ValueType: Devirtualize with EqualityComparer.Default intrinsic @@ -428,9 +431,9 @@ private int FindEntry(TKey key) } else { - int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; + uint hashCode = (uint)comparer.GetHashCode(key); // Value in _buckets is 1-based - i = buckets[hashCode % buckets.Length] - 1; + i = buckets[hashCode % (uint)buckets.Length] - 1; do { // Should be a while loop https://github.com/dotnet/coreclr/issues/15476 @@ -482,10 +485,10 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) Entry[] entries = _entries; IEqualityComparer comparer = _comparer; - int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & 0x7FFFFFFF; + uint hashCode = (uint)((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)); int collisionCount = 0; - ref int bucket = ref _buckets[hashCode % _buckets.Length]; + ref int bucket = ref _buckets[hashCode % (uint)_buckets.Length]; // Value in _buckets is 1-based int i = bucket - 1; @@ -627,7 +630,7 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) if (count == entries.Length) { Resize(); - bucket = ref _buckets[hashCode % _buckets.Length]; + bucket = ref _buckets[hashCode % (uint)_buckets.Length]; } index = count; _count = count + 1; @@ -638,7 +641,9 @@ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior) if (updateFreeList) { - _freeList = entry.next; + Debug.Assert((StartOfFreeList - entries[_freeList].next) >= -1, "shouldn't overflow because `next` cannot underflow"); + + _freeList = StartOfFreeList - entries[_freeList].next; } entry.hashCode = hashCode; // Value in _buckets is 1-based @@ -725,19 +730,19 @@ private void Resize(int newSize, bool forceNewHashCodes) { for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { Debug.Assert(_comparer == null); - entries[i].hashCode = (entries[i].key.GetHashCode() & 0x7FFFFFFF); + entries[i].hashCode = (uint)entries[i].key.GetHashCode(); } } } for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { - int bucket = entries[i].hashCode % newSize; + uint bucket = entries[i].hashCode % (uint)newSize; // Value in _buckets is 1-based entries[i].next = buckets[bucket] - 1; // Value in _buckets is 1-based @@ -764,8 +769,8 @@ public bool Remove(TKey key) int collisionCount = 0; if (buckets != null) { - int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF; - int bucket = hashCode % buckets.Length; + uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); + uint bucket = hashCode % (uint)buckets.Length; int last = -1; // Value in buckets is 1-based int i = buckets[bucket] - 1; @@ -784,8 +789,10 @@ public bool Remove(TKey key) { entries[last].next = entry.next; } - entry.hashCode = -1; - entry.next = _freeList; + + Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + + entry.next = StartOfFreeList - _freeList; if (RuntimeHelpers.IsReferenceOrContainsReferences()) { @@ -829,8 +836,8 @@ public bool Remove(TKey key, out TValue value) int collisionCount = 0; if (buckets != null) { - int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & 0x7FFFFFFF; - int bucket = hashCode % buckets.Length; + uint hashCode = (uint)(_comparer?.GetHashCode(key) ?? key.GetHashCode()); + uint bucket = hashCode % (uint)buckets.Length; int last = -1; // Value in buckets is 1-based int i = buckets[bucket] - 1; @@ -852,8 +859,9 @@ public bool Remove(TKey key, out TValue value) value = entry.value; - entry.hashCode = -1; - entry.next = _freeList; + Debug.Assert((StartOfFreeList - _freeList) < 0, "shouldn't underflow because max hashtable length is MaxPrimeArrayLength = 0x7FEFFFFD(2146435069) _freelist underflow threshold 2147483646"); + + entry.next = StartOfFreeList - _freeList; if (RuntimeHelpers.IsReferenceOrContainsReferences()) { @@ -925,7 +933,7 @@ void ICollection.CopyTo(Array array, int index) Entry[] entries = _entries; for (int i = 0; i < _count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value); } @@ -945,7 +953,7 @@ void ICollection.CopyTo(Array array, int index) Entry[] entries = _entries; for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) + if (entries[i].next >= -1) { objects[index++] = new KeyValuePair(entries[i].key, entries[i].value); } @@ -1018,12 +1026,12 @@ public void TrimExcess(int capacity) int count = 0; for (int i = 0; i < oldCount; i++) { - int hashCode = oldEntries[i].hashCode; - if (hashCode >= 0) + uint hashCode = oldEntries[i].hashCode; + if (oldEntries[i].next >= -1) { ref Entry entry = ref entries[count]; entry = oldEntries[i]; - int bucket = hashCode % newSize; + uint bucket = hashCode % (uint)newSize; // Value in _buckets is 1-based entry.next = buckets[bucket] - 1; // Value in _buckets is 1-based @@ -1179,7 +1187,7 @@ public bool MoveNext() { ref Entry entry = ref _dictionary._entries[_index++]; - if (entry.hashCode >= 0) + if (entry.next >= -1) { _current = new KeyValuePair(entry.key, entry.value); return true; @@ -1307,7 +1315,7 @@ public void CopyTo(TKey[] array, int index) Entry[] entries = _dictionary._entries; for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) array[index++] = entries[i].key; + if (entries[i].next >= -1) array[index++] = entries[i].key; } } @@ -1367,7 +1375,7 @@ void ICollection.CopyTo(Array array, int index) { for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) objects[index++] = entries[i].key; + if (entries[i].next >= -1) objects[index++] = entries[i].key; } } catch (ArrayTypeMismatchException) @@ -1411,7 +1419,7 @@ public bool MoveNext() { ref Entry entry = ref _dictionary._entries[_index++]; - if (entry.hashCode >= 0) + if (entry.next >= -1) { _currentKey = entry.key; return true; @@ -1490,7 +1498,7 @@ public void CopyTo(TValue[] array, int index) Entry[] entries = _dictionary._entries; for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) array[index++] = entries[i].value; + if (entries[i].next >= -1) array[index++] = entries[i].value; } } @@ -1550,7 +1558,7 @@ void ICollection.CopyTo(Array array, int index) { for (int i = 0; i < count; i++) { - if (entries[i].hashCode >= 0) objects[index++] = entries[i].value; + if (entries[i].next >= -1) objects[index++] = entries[i].value; } } catch (ArrayTypeMismatchException) @@ -1594,7 +1602,7 @@ public bool MoveNext() { ref Entry entry = ref _dictionary._entries[_index++]; - if (entry.hashCode >= 0) + if (entry.next >= -1) { _currentValue = entry.value; return true; diff --git a/src/Common/src/CoreLib/System/ComponentModel/DefaultValueAttribute.cs b/src/Common/src/CoreLib/System/ComponentModel/DefaultValueAttribute.cs index 136e4324ed74..fb2b49c94c10 100644 --- a/src/Common/src/CoreLib/System/ComponentModel/DefaultValueAttribute.cs +++ b/src/Common/src/CoreLib/System/ComponentModel/DefaultValueAttribute.cs @@ -2,39 +2,38 @@ // 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.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Threading; namespace System.ComponentModel { - /// - /// Specifies the default value for a property. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] + /// + /// Specifies the default value for a property. + /// + [SuppressMessage("Microsoft.Design", "CA1019:DefineAccessorsForAttributeArguments")] + [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes")] [AttributeUsage(AttributeTargets.All)] public class DefaultValueAttribute : Attribute { - /// - /// This is the default value. - /// + /// + /// This is the default value. + /// private object _value; // Delegate ad hoc created 'TypeDescriptor.ConvertFromInvariantString' reflection object cache - static object s_convertFromInvariantString; + private static object s_convertFromInvariantString; - /// - /// Initializes a new instance of the class, converting the - /// specified value to the - /// specified type, and using the U.S. English culture as the - /// translation - /// context. - /// + /// + /// Initializes a new instance of the + /// class, converting the specified value to the specified type, and using the U.S. English + /// culture as the translation context. + /// public DefaultValueAttribute(Type type, string value) { - // The try/catch here is because attributes should never throw exceptions. We would fail to - // load an otherwise normal class. + // The try/catch here is because attributes should never throw exceptions. + // We would fail to load an otherwise normal class. try { if (TryConvertFromInvariantString(type, value, out object convertedValue)) @@ -54,8 +53,6 @@ public DefaultValueAttribute(Type type, string value) _value = Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } - return; - // Looking for ad hoc created TypeDescriptor.ConvertFromInvariantString(Type, string) bool TryConvertFromInvariantString(Type typeToConvert, string stringValue, out object conversionResult) { @@ -89,143 +86,140 @@ bool TryConvertFromInvariantString(Type typeToConvert, string stringValue, out o } } - /// - /// Initializes a new instance of the class using a Unicode - /// character. - /// + /// + /// Initializes a new instance of the + /// class using a Unicode character. + /// public DefaultValueAttribute(char value) { _value = value; } - /// - /// Initializes a new instance of the class using an 8-bit unsigned - /// integer. - /// + + /// + /// Initializes a new instance of the + /// class using an 8-bit unsigned integer. + /// public DefaultValueAttribute(byte value) { _value = value; } - /// - /// Initializes a new instance of the class using a 16-bit signed - /// integer. - /// + + /// + /// Initializes a new instance of the + /// class using a 16-bit signed integer. + /// public DefaultValueAttribute(short value) { _value = value; } - /// - /// Initializes a new instance of the class using a 32-bit signed - /// integer. - /// + + /// + /// Initializes a new instance of the + /// class using a 32-bit signed integer. + /// public DefaultValueAttribute(int value) { _value = value; } - /// - /// Initializes a new instance of the class using a 64-bit signed - /// integer. - /// + + /// + /// Initializes a new instance of the + /// class using a 64-bit signed integer. + /// public DefaultValueAttribute(long value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// single-precision floating point - /// number. - /// + + /// + /// Initializes a new instance of the + /// class using a single-precision floating point number. + /// public DefaultValueAttribute(float value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// double-precision floating point - /// number. - /// + + /// + /// Initializes a new instance of the + /// class using a double-precision floating point number. + /// public DefaultValueAttribute(double value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// value. - /// + + /// + /// Initializes a new instance of the + /// class using a value. + /// public DefaultValueAttribute(bool value) { _value = value; } - /// - /// Initializes a new instance of the class using a . - /// + + /// + /// Initializes a new instance of the + /// class using a . + /// public DefaultValueAttribute(string value) { _value = value; } - /// - /// Initializes a new instance of the - /// class. - /// + /// + /// Initializes a new instance of the + /// class. + /// public DefaultValueAttribute(object value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// value. - /// + /// + /// Initializes a new instance of the + /// class using a value. + /// [CLSCompliant(false)] public DefaultValueAttribute(sbyte value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// value. - /// + /// + /// Initializes a new instance of the + /// class using a value. + /// [CLSCompliant(false)] public DefaultValueAttribute(ushort value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// value. - /// + /// + /// Initializes a new instance of the + /// class using a value. + /// [CLSCompliant(false)] public DefaultValueAttribute(uint value) { _value = value; } - /// - /// Initializes a new instance of the class using a - /// value. - /// + /// + /// Initializes a new instance of the + /// class using a value. + /// [CLSCompliant(false)] public DefaultValueAttribute(ulong value) { _value = value; } - /// - /// - /// Gets the default value of the property this - /// attribute is - /// bound to. - /// - /// - public virtual object Value - { - get - { - return _value; - } - } + /// + /// Gets the default value of the property this attribute is bound to. + /// + public virtual object Value => _value; public override bool Equals(object obj) { @@ -233,29 +227,21 @@ public override bool Equals(object obj) { return true; } - - if (obj is DefaultValueAttribute other) + if (!(obj is DefaultValueAttribute other)) { - if (Value != null) - { - return Value.Equals(other.Value); - } - else - { - return (other.Value == null); - } + return false; + } + + if (Value == null) + { + return other.Value == null; } - return false; - } - public override int GetHashCode() - { - return base.GetHashCode(); + return Value.Equals(other.Value); } - protected void SetValue(object value) - { - _value = value; - } + public override int GetHashCode() => base.GetHashCode(); + + protected void SetValue(object value) => _value = value; } } diff --git a/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableAttribute.cs b/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableAttribute.cs index a59ee839bc40..de69538532f6 100644 --- a/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableAttribute.cs +++ b/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableAttribute.cs @@ -7,20 +7,17 @@ namespace System.ComponentModel [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Delegate | AttributeTargets.Interface)] public sealed class EditorBrowsableAttribute : Attribute { - private EditorBrowsableState browsableState; - public EditorBrowsableAttribute(EditorBrowsableState state) { - browsableState = state; + State = state; } - public EditorBrowsableAttribute() : this(EditorBrowsableState.Always) { } - - public EditorBrowsableState State + public EditorBrowsableAttribute() : this(EditorBrowsableState.Always) { - get { return browsableState; } } + public EditorBrowsableState State { get; } + public override bool Equals(object obj) { if (obj == this) @@ -28,19 +25,9 @@ public override bool Equals(object obj) return true; } - return (obj is EditorBrowsableAttribute other) && other.browsableState == browsableState; - } - - public override int GetHashCode() - { - return base.GetHashCode(); + return (obj is EditorBrowsableAttribute other) && other.State == State; } - } - public enum EditorBrowsableState - { - Always, - Never, - Advanced + public override int GetHashCode() => base.GetHashCode(); } } diff --git a/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableState.cs b/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableState.cs new file mode 100644 index 000000000000..a98669c4e963 --- /dev/null +++ b/src/Common/src/CoreLib/System/ComponentModel/EditorBrowsableState.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.ComponentModel +{ + public enum EditorBrowsableState + { + Always, + Never, + Advanced + } +} diff --git a/src/Common/src/CoreLib/System/DateTime.Unix.cs b/src/Common/src/CoreLib/System/DateTime.Unix.cs index 6cf018115bac..2c4de3e1a85e 100644 --- a/src/Common/src/CoreLib/System/DateTime.Unix.cs +++ b/src/Common/src/CoreLib/System/DateTime.Unix.cs @@ -18,8 +18,8 @@ public static DateTime UtcNow } #endif - internal static DateTime FromFileTimeLeapSecondsAware(long fileTime) => default; - internal static long ToFileTimeLeapSecondsAware(long ticks) => default; + private static DateTime FromFileTimeLeapSecondsAware(long fileTime) => default; + private static long ToFileTimeLeapSecondsAware(long ticks) => default; // IsValidTimeWithLeapSeconds is not expected to be called at all for now on non-Windows platforms internal static bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) => false; diff --git a/src/Common/src/CoreLib/System/DateTime.Win32.cs b/src/Common/src/CoreLib/System/DateTime.Win32.cs new file mode 100644 index 000000000000..d742c891c7c0 --- /dev/null +++ b/src/Common/src/CoreLib/System/DateTime.Win32.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + public readonly partial struct DateTime + { + private static unsafe bool SystemSupportsLeapSeconds() + { + Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION slsi; + + return Interop.NtDll.NtQuerySystemInformation( + Interop.NtDll.SystemLeapSecondInformation, + (void *) &slsi, + sizeof(Interop.NtDll.SYSTEM_LEAP_SECOND_INFORMATION), + null) == 0 && slsi.Enabled; + } + } +} diff --git a/src/Common/src/CoreLib/System/DateTime.WinRT.cs b/src/Common/src/CoreLib/System/DateTime.WinRT.cs new file mode 100644 index 000000000000..30a9a61aa3da --- /dev/null +++ b/src/Common/src/CoreLib/System/DateTime.WinRT.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + public readonly partial struct DateTime + { + private static unsafe bool SystemSupportsLeapSeconds() + { + Interop.Kernel32.PROCESS_LEAP_SECOND_INFO info; + + // Store apps don't have access to an API that would let us find out whether leap seconds have been + // disabled by policy: this implementation will produce slightly different results from what + // we have for Win32. If GetProcessInformation succeeds, we have to act as if leap seconds existed. + // They could still have been disabled by policy, but we have no way to check for that. + return Interop.Kernel32.GetProcessInformation( + Interop.Kernel32.GetCurrentProcess(), + Interop.Kernel32.ProcessLeapSecondInfo, + &info, + sizeof(Interop.Kernel32.PROCESS_LEAP_SECOND_INFO)) != Interop.BOOL.FALSE; + } + } +} diff --git a/src/Common/src/CoreLib/System/DateTime.Windows.cs b/src/Common/src/CoreLib/System/DateTime.Windows.cs new file mode 100644 index 000000000000..ba9df5c4536b --- /dev/null +++ b/src/Common/src/CoreLib/System/DateTime.Windows.cs @@ -0,0 +1,216 @@ +// 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.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace System +{ + public readonly partial struct DateTime + { + internal static readonly bool s_systemSupportsLeapSeconds = SystemSupportsLeapSeconds(); + + public static unsafe DateTime UtcNow + { + get + { + if (s_systemSupportsLeapSeconds) + { + FullSystemTime time; + GetSystemTimeWithLeapSecondsHandling(&time); + return CreateDateTimeFromSystemTime(in time); + } + + return new DateTime(((ulong)(GetSystemTimeAsFileTime() + FileTimeOffset)) | KindUtc); + } + } + + internal static unsafe bool IsValidTimeWithLeapSeconds(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + { + DateTime dt = new DateTime(year, month, day); + FullSystemTime time = new FullSystemTime(year, month, dt.DayOfWeek, day, hour, minute, second); + + switch (kind) + { + case DateTimeKind.Local: return ValidateSystemTime(&time.systemTime, localTime: true); + case DateTimeKind.Utc: return ValidateSystemTime(&time.systemTime, localTime: false); + default: + return ValidateSystemTime(&time.systemTime, localTime: true) || ValidateSystemTime(&time.systemTime, localTime: false); + } + } + + private static unsafe DateTime FromFileTimeLeapSecondsAware(long fileTime) + { + FullSystemTime time; + if (FileTimeToSystemTime(fileTime, &time)) + { + return CreateDateTimeFromSystemTime(in time); + } + + throw new ArgumentOutOfRangeException(nameof(fileTime), SR.ArgumentOutOfRange_DateTimeBadTicks); + } + + private static unsafe long ToFileTimeLeapSecondsAware(long ticks) + { + FullSystemTime time = new FullSystemTime(ticks); + long fileTime; + + if (SystemTimeToFileTime(&time.systemTime, &fileTime)) + { + return fileTime + ticks % TicksPerMillisecond; + } + + throw new ArgumentOutOfRangeException(null, SR.ArgumentOutOfRange_FileTimeInvalid); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static DateTime CreateDateTimeFromSystemTime(in FullSystemTime time) + { + long ticks = DateToTicks(time.systemTime.Year, time.systemTime.Month, time.systemTime.Day); + ticks += TimeToTicks(time.systemTime.Hour, time.systemTime.Minute, time.systemTime.Second); + ticks += time.systemTime.Milliseconds * TicksPerMillisecond; + ticks += time.hundredNanoSecond; + return new DateTime( ((UInt64)(ticks)) | KindUtc); + } + + // FullSystemTime struct is the SYSTEMTIME struct with extra hundredNanoSecond field to store more precise time. + [StructLayout(LayoutKind.Sequential)] + private struct FullSystemTime + { + internal Interop.Kernel32.SYSTEMTIME systemTime; + internal long hundredNanoSecond; + + internal FullSystemTime(int year, int month, DayOfWeek dayOfWeek, int day, int hour, int minute, int second) + { + systemTime.Year = (ushort) year; + systemTime.Month = (ushort) month; + systemTime.DayOfWeek = (ushort) dayOfWeek; + systemTime.Day = (ushort) day; + systemTime.Hour = (ushort) hour; + systemTime.Minute = (ushort) minute; + systemTime.Second = (ushort) second; + systemTime.Milliseconds = 0; + hundredNanoSecond = 0; + } + + internal FullSystemTime(long ticks) + { + DateTime dt = new DateTime(ticks); + + int year, month, day; + dt.GetDatePart(out year, out month, out day); + + systemTime.Year = (ushort) year; + systemTime.Month = (ushort) month; + systemTime.DayOfWeek = (ushort) dt.DayOfWeek; + systemTime.Day = (ushort) day; + systemTime.Hour = (ushort) dt.Hour; + systemTime.Minute = (ushort) dt.Minute; + systemTime.Second = (ushort) dt.Second; + systemTime.Milliseconds = (ushort) dt.Millisecond; + hundredNanoSecond = 0; + } + }; + +#if !CORECLR + internal static readonly bool s_systemSupportsPreciseSystemTime = SystemSupportsPreciseSystemTime(); + + private static unsafe bool SystemSupportsPreciseSystemTime() + { + if (Environment.IsWindows8OrAbove) + { + // GetSystemTimePreciseAsFileTime exists and we'd like to use it. However, on + // misconfigured systems, it's possible for the "precise" time to be inaccurate: + // https://github.com/dotnet/coreclr/issues/14187 + // If it's inaccurate, though, we expect it to be wildly inaccurate, so as a + // workaround/heuristic, we get both the "normal" and "precise" times, and as + // long as they're close, we use the precise one. This workaround can be removed + // when we better understand what's causing the drift and the issue is no longer + // a problem or can be better worked around on all targeted OSes. + + long systemTimeResult; + Interop.Kernel32.GetSystemTimeAsFileTime(&systemTimeResult); + + long preciseSystemTimeResult; + Interop.Kernel32.GetSystemTimePreciseAsFileTime(&preciseSystemTimeResult); + + return Math.Abs(preciseSystemTimeResult - systemTimeResult) <= 100 * TicksPerMillisecond; + } + + return false; + } + + private static unsafe bool ValidateSystemTime(Interop.Kernel32.SYSTEMTIME* time, bool localTime) + { + if (localTime) + { + Interop.Kernel32.SYSTEMTIME st; + return Interop.Kernel32.TzSpecificLocalTimeToSystemTime(IntPtr.Zero, time, &st) != Interop.BOOL.FALSE; + } + else + { + long timestamp; + return Interop.Kernel32.SystemTimeToFileTime(time, ×tamp) != Interop.BOOL.FALSE; + } + } + + private static unsafe bool FileTimeToSystemTime(long fileTime, FullSystemTime* time) + { + if (Interop.Kernel32.FileTimeToSystemTime(&fileTime, &time->systemTime) != Interop.BOOL.FALSE) + { + // to keep the time precision + time->hundredNanoSecond = fileTime % TicksPerMillisecond; + if (time->systemTime.Second > 59) + { + // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation. + // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds + time->systemTime.Second = 59; + time->systemTime.Milliseconds = 999; + time->hundredNanoSecond = 9999; + } + return true; + } + return false; + } + + private static unsafe void GetSystemTimeWithLeapSecondsHandling(FullSystemTime* time) + { + if (!FileTimeToSystemTime(GetSystemTimeAsFileTime(), time)) + { + Interop.Kernel32.GetSystemTime(&time->systemTime); + time->hundredNanoSecond = 0; + if (time->systemTime.Second > 59) + { + // we have a leap second, force it to last second in the minute as DateTime doesn't account for leap seconds in its calculation. + // we use the maxvalue from the milliseconds and the 100-nano seconds to avoid reporting two out of order 59 seconds + time->systemTime.Second = 59; + time->systemTime.Milliseconds = 999; + time->hundredNanoSecond = 9999; + } + } + } + + private static unsafe bool SystemTimeToFileTime(Interop.Kernel32.SYSTEMTIME* time, long* fileTime) + { + return Interop.Kernel32.SystemTimeToFileTime(time, fileTime) != Interop.BOOL.FALSE; + } + + private static unsafe long GetSystemTimeAsFileTime() + { + long timestamp; + + if (s_systemSupportsPreciseSystemTime) + { + Interop.Kernel32.GetSystemTimePreciseAsFileTime(×tamp); + } + else + { + Interop.Kernel32.GetSystemTimeAsFileTime(×tamp); + } + + return timestamp; + } +#endif + } +} diff --git a/src/Common/src/CoreLib/System/Delegate.cs b/src/Common/src/CoreLib/System/Delegate.cs index 1e8b8c967a7f..0c60ba5c40fb 100644 --- a/src/Common/src/CoreLib/System/Delegate.cs +++ b/src/Common/src/CoreLib/System/Delegate.cs @@ -11,10 +11,6 @@ namespace System { public abstract partial class Delegate : ICloneable, ISerializable { - private Delegate() - { - } - public virtual object Clone() => MemberwiseClone(); public static Delegate? Combine(Delegate? a, Delegate? b) @@ -37,8 +33,6 @@ private Delegate() return d; } - protected virtual Delegate CombineImpl(Delegate? d) => throw new MulticastNotSupportedException(SR.Multicast_Combine); - // V2 api: Creates open or closed delegates to static or instance methods - relaxed signature checking allowed. public static Delegate CreateDelegate(Type type, object? firstArgument, MethodInfo method) => CreateDelegate(type, firstArgument, method, throwOnBindFailure: true)!; @@ -53,19 +47,23 @@ private Delegate() public static Delegate CreateDelegate(Type type, Type target, string method) => CreateDelegate(type, target, method, ignoreCase: false, throwOnBindFailure: true)!; public static Delegate CreateDelegate(Type type, 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; + + public virtual Delegate[] GetInvocationList() => new Delegate[] { this }; + public object? DynamicInvoke(params object?[]? args) { return DynamicInvokeImpl(args); } - - public virtual Delegate[] GetInvocationList() => new Delegate[] { this }; +#endif public virtual void GetObjectData(SerializationInfo info, StreamingContext context) => throw new PlatformNotSupportedException(); public MethodInfo Method => GetMethodImpl(); - protected virtual Delegate? RemoveImpl(Delegate d) => d.Equals(this) ? null : this; - public static Delegate? Remove(Delegate? source, Delegate? value) { if (source == null) diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterGroup.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterGroup.cs index 010cbd508fb3..0382556f3b56 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterGroup.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterGroup.cs @@ -20,22 +20,22 @@ namespace System.Diagnostics.Tracing internal class CounterGroup { private readonly EventSource _eventSource; - private readonly List _counters; + private readonly List _counters; internal CounterGroup(EventSource eventSource) { _eventSource = eventSource; - _counters = new List(); + _counters = new List(); RegisterCommandCallback(); } - internal void Add(BaseCounter eventCounter) + internal void Add(DiagnosticCounter eventCounter) { lock (this) // Lock the CounterGroup _counters.Add(eventCounter); } - internal void Remove(BaseCounter eventCounter) + internal void Remove(DiagnosticCounter eventCounter) { lock (this) // Lock the CounterGroup _counters.Remove(eventCounter); diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterPayload.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterPayload.cs index ae4a1a7280f2..1be7d5494ae9 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterPayload.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/CounterPayload.cs @@ -17,52 +17,6 @@ namespace Microsoft.Diagnostics.Tracing namespace System.Diagnostics.Tracing #endif { - // TODO: This should be removed as we make the new payloads public - [EventData] - internal class EventCounterPayload : IEnumerable> - { - public string Name { get; set; } - - public float Mean { get; set; } - - public float StandardDeviation { get; set; } - - public int Count { get; set; } - - public float Min { get; set; } - - public float Max { get; set; } - - public float IntervalSec { get; internal set; } - - #region Implementation of the IEnumerable interface - - public IEnumerator> GetEnumerator() - { - return ForEnumeration.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ForEnumeration.GetEnumerator(); - } - - private IEnumerable> ForEnumeration - { - get - { - yield return new KeyValuePair("Name", Name); - yield return new KeyValuePair("Mean", Mean); - yield return new KeyValuePair("StandardDeviation", StandardDeviation); - yield return new KeyValuePair("Count", Count); - yield return new KeyValuePair("Min", Min); - yield return new KeyValuePair("Max", Max); - } - } - - #endregion // Implementation of the IEnumerable interface - } - [EventData] internal class CounterPayload : IEnumerable> { @@ -70,19 +24,19 @@ internal class CounterPayload : IEnumerable> public string DisplayName { get; set; } - public float Mean { get; set; } + public double Mean { get; set; } - public float StandardDeviation { get; set; } + public double StandardDeviation { get; set; } public int Count { get; set; } - public float Min { get; set; } + public double Min { get; set; } - public float Max { get; set; } + public double Max { get; set; } public float IntervalSec { get; internal set; } - public string MetaData { get; set; } + public string Metadata { get; set; } #region Implementation of the IEnumerable interface @@ -110,7 +64,7 @@ private IEnumerable> ForEnumeration yield return new KeyValuePair("IntervalSec", IntervalSec); yield return new KeyValuePair("Series", $"Interval={IntervalSec}"); yield return new KeyValuePair("CounterType", "Mean"); - yield return new KeyValuePair("MetaData", MetaData); + yield return new KeyValuePair("Metadata", Metadata); } } @@ -126,11 +80,11 @@ internal class IncrementingCounterPayload : IEnumerable> ForEnumeration yield return new KeyValuePair("IntervalSec", IntervalSec); yield return new KeyValuePair("Series", $"Interval={IntervalSec}"); yield return new KeyValuePair("CounterType", "Sum"); - yield return new KeyValuePair("MetaData", MetaData); + yield return new KeyValuePair("Metadata", Metadata); } } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/BaseCounter.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/DiagnosticCounter.cs similarity index 71% rename from src/Common/src/CoreLib/System/Diagnostics/Tracing/BaseCounter.cs rename to src/Common/src/CoreLib/System/Diagnostics/Tracing/DiagnosticCounter.cs index 447852e84a99..ea4cb92612b1 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/BaseCounter.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/DiagnosticCounter.cs @@ -19,10 +19,10 @@ namespace System.Diagnostics.Tracing #endif { /// - /// BaseCounter is an abstract class that serves as the parent class for various Counter* classes, + /// DiagnosticCounter is an abstract class that serves as the parent class for various Counter* classes, /// namely EventCounter, PollingCounter, IncrementingEventCounter, and IncrementingPollingCounter. /// - public abstract class BaseCounter : IDisposable + public abstract class DiagnosticCounter : IDisposable { /// /// All Counters live as long as the EventSource that they are attached to unless they are @@ -30,23 +30,22 @@ public abstract class BaseCounter : IDisposable /// /// The name. /// The event source. - public BaseCounter(string name, EventSource eventSource) + public DiagnosticCounter(string name, EventSource eventSource) { if (name == null) { - throw new ArgumentNullException(nameof(_name)); + throw new ArgumentNullException(nameof(Name)); } if (eventSource == null) { - throw new ArgumentNullException(nameof(eventSource)); + throw new ArgumentNullException(nameof(EventSource)); } _group = CounterGroup.GetCounterGroup(eventSource); _group.Add(this); - _eventSource = eventSource; - _name = name; - _metaData = new Dictionary(); + Name = name; + EventSource = eventSource; } /// @@ -67,38 +66,45 @@ public void Dispose() /// /// Adds a key-value metadata to the EventCounter that will be included as a part of the payload /// - internal void AddMetaData(string key, string value) + public void AddMetadata(string key, string value) { lock (MyLock) { - _metaData.Add(key, value); + _metadata = _metadata ?? new Dictionary(); + _metadata.Add(key, value); } } - internal string DisplayName { get; set; } + public string DisplayName { get; set; } - #region private implementation + public string Name { get; } + + public EventSource EventSource { get; } - internal readonly string _name; + #region private implementation private CounterGroup _group; - private Dictionary _metaData; - internal EventSource _eventSource; + private Dictionary _metadata; internal abstract void WritePayload(float intervalSec); // arbitrarily we use name as the lock object. - internal object MyLock { get { return _name; } } + internal object MyLock { get { return Name; } } internal void ReportOutOfBandMessage(string message) { - _eventSource.ReportOutOfBandMessage(message, true); + EventSource.ReportOutOfBandMessage(message, true); } - internal string GetMetaDataString() + internal string GetMetadataString() { + if (_metadata == null) + { + return ""; + } + StringBuilder sb = new StringBuilder(""); - foreach(KeyValuePair kvPair in _metaData) + foreach(KeyValuePair kvPair in _metadata) { sb.Append($"{kvPair.Key}:{kvPair.Value},"); } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventCounter.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventCounter.cs index 85fc40a08cc6..a9fc0895c9a8 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventCounter.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventCounter.cs @@ -26,7 +26,7 @@ namespace System.Diagnostics.Tracing /// See https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.Tracing/tests/BasicEventSourceTest/TestEventCounter.cs /// which shows tests, which are also useful in seeing actual use. /// - public partial class EventCounter : BaseCounter + public partial class EventCounter : DiagnosticCounter { /// /// Initializes a new instance of the class. @@ -37,8 +37,8 @@ public partial class EventCounter : BaseCounter /// The event source. public EventCounter(string name, EventSource eventSource) : base(name, eventSource) { - _min = float.PositiveInfinity; - _max = float.NegativeInfinity; + _min = double.PositiveInfinity; + _max = double.NegativeInfinity; InitializeBuffer(); } @@ -49,22 +49,27 @@ public EventCounter(string name, EventSource eventSource) : base(name, eventSour /// /// The value. public void WriteMetric(float value) + { + Enqueue((double)value); + } + + public void WriteMetric(double value) { Enqueue(value); } - public override string ToString() => $"EventCounter '{_name}' Count {_count} Mean {(((double)_sum) / _count).ToString("n3")}"; + public override string ToString() => $"EventCounter '{Name}' Count {_count} Mean {(_sum / _count).ToString("n3")}"; #region Statistics Calculation // Statistics private int _count; - private float _sum; - private float _sumSquared; - private float _min; - private float _max; + private double _sum; + private double _sumSquared; + private double _min; + private double _max; - internal void OnMetricWritten(float value) + internal void OnMetricWritten(double value) { Debug.Assert(Monitor.IsEntered(MyLock)); _sum += value; @@ -83,14 +88,13 @@ internal override void WritePayload(float intervalSec) lock (MyLock) { Flush(); - EventCounterPayload payload = new EventCounterPayload(); - payload.Name = _name; + CounterPayload payload = new CounterPayload(); payload.Count = _count; payload.IntervalSec = intervalSec; if (0 < _count) { payload.Mean = _sum / _count; - payload.StandardDeviation = (float)Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count); + payload.StandardDeviation = Math.Sqrt(_sumSquared / _count - _sum * _sum / _count / _count); } else { @@ -99,8 +103,12 @@ internal override void WritePayload(float intervalSec) } payload.Min = _min; payload.Max = _max; + + payload.Metadata = GetMetadataString(); + payload.DisplayName = DisplayName; + payload.Name = Name; ResetStatistics(); - _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new EventCounterPayloadType(payload)); + EventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new CounterPayloadType(payload)); } } private void ResetStatistics() @@ -109,35 +117,35 @@ private void ResetStatistics() _count = 0; _sum = 0; _sumSquared = 0; - _min = float.PositiveInfinity; - _max = float.NegativeInfinity; + _min = double.PositiveInfinity; + _max = double.NegativeInfinity; } #endregion // Statistics Calculation // Values buffering private const int BufferedSize = 10; - private const float UnusedBufferSlotValue = float.NegativeInfinity; + private const double UnusedBufferSlotValue = double.NegativeInfinity; private const int UnsetIndex = -1; - private volatile float[] _bufferedValues; + private volatile double[] _bufferedValues; private volatile int _bufferedValuesIndex; private void InitializeBuffer() { - _bufferedValues = new float[BufferedSize]; + _bufferedValues = new double[BufferedSize]; for (int i = 0; i < _bufferedValues.Length; i++) { _bufferedValues[i] = UnusedBufferSlotValue; } } - protected void Enqueue(float value) + private void Enqueue(double value) { // It is possible that two threads read the same bufferedValuesIndex, but only one will be able to write the slot, so that is okay. int i = _bufferedValuesIndex; while (true) { - float result = Interlocked.CompareExchange(ref _bufferedValues[i], value, UnusedBufferSlotValue); + double result = Interlocked.CompareExchange(ref _bufferedValues[i], value, UnusedBufferSlotValue); i++; if (_bufferedValues.Length <= i) { @@ -178,10 +186,10 @@ protected void Flush() /// This is the payload that is sent in the with EventSource.Write /// [EventData] - class EventCounterPayloadType + class CounterPayloadType { - public EventCounterPayloadType(EventCounterPayload payload) { Payload = payload; } - public EventCounterPayload Payload { get; set; } + public CounterPayloadType(CounterPayload payload) { Payload = payload; } + public CounterPayload Payload { get; set; } } } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventProvider.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventProvider.cs index 345a9cf66cd9..a8622a8fb817 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventProvider.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventProvider.cs @@ -63,7 +63,7 @@ internal enum ControllerCommand #if ES_BUILD_STANDALONE [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] #endif - internal partial class EventProvider : IDisposable + internal class EventProvider : IDisposable { // This is the windows EVENT_DATA_DESCRIPTOR structure. We expose it because this is what // subclasses of EventProvider use when creating efficient (but unsafe) version of @@ -92,10 +92,8 @@ internal SessionInfo(int sessionIdBit_, int etwSessionId_) { sessionIdBit = sessionIdBit_; etwSessionId = etwSessionId_; } } - private static bool m_setInformationMissing; - internal IEventProvider m_eventProvider; // The interface that implements the specific logging mechanism functions. - UnsafeNativeMethods.ManifestEtw.EtwEnableCallback m_etwCallback; // Trace Callback function + Interop.Advapi32.EtwEnableCallback m_etwCallback; // Trace Callback function private long m_regHandle; // Trace Registration Handle private byte m_level; // Tracing Level private long m_anyKeywordMask; // Trace Enable Flags @@ -165,14 +163,14 @@ internal EventProvider(EventProviderType providerType) /// reason the ETW Register call failed a NotSupported exception will be thrown. /// // - // + // // // // internal unsafe void Register(EventSource eventSource) { uint status; - m_etwCallback = new UnsafeNativeMethods.ManifestEtw.EtwEnableCallback(EtwEnableCallBack); + m_etwCallback = new Interop.Advapi32.EtwEnableCallback(EtwEnableCallBack); status = EventRegister(eventSource, m_etwCallback); if (status != 0) @@ -264,13 +262,13 @@ public virtual void Close() // // unsafe void EtwEnableCallBack( - [In] ref System.Guid sourceId, - [In] int controlCode, - [In] byte setLevel, - [In] long anyKeyword, - [In] long allKeyword, - [In] UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, - [In] void* callbackContext + in System.Guid sourceId, + int controlCode, + byte setLevel, + long anyKeyword, + long allKeyword, + Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData, + void* callbackContext ) { // This is an optional callback API. We will therefore ignore any failures that happen as a @@ -281,7 +279,7 @@ [In] void* callbackContext ControllerCommand command = ControllerCommand.Update; IDictionary args = null; bool skipFinalOnControllerCommand = false; - if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_ENABLE_PROVIDER) + if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_ENABLE_PROVIDER) { m_enabled = true; m_level = setLevel; @@ -342,7 +340,7 @@ [In] void* callbackContext OnControllerCommand(command, args, (bEnabling ? sessionChanged : -sessionChanged), etwSessionId); } } - else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_DISABLE_PROVIDER) + else if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_DISABLE_PROVIDER) { m_enabled = false; m_level = 0; @@ -350,7 +348,7 @@ [In] void* callbackContext m_allKeywordMask = 0; m_liveSessions = null; } - else if (controlCode == UnsafeNativeMethods.ManifestEtw.EVENT_CONTROL_CODE_CAPTURE_STATE) + else if (controlCode == Interop.Advapi32.EVENT_CONTROL_CODE_CAPTURE_STATE) { command = ControllerCommand.SendManifest; } @@ -491,24 +489,24 @@ private unsafe void GetSessionInfo(SessionInfoCallback action, ref ListInstanceCount; i++) { if (providerInstance->Pid == processId) { - var enabledInfos = (UnsafeNativeMethods.ManifestEtw.TRACE_ENABLE_INFO*)&providerInstance[1]; + var enabledInfos = (Interop.Advapi32.TRACE_ENABLE_INFO*)&providerInstance[1]; // iterate over the list of active ETW sessions "listening" to the current provider for (int j = 0; j < providerInstance->EnableCount; j++) action(enabledInfos[j].LoggerId, enabledInfos[j].MatchAllKeyword, ref sessionList); @@ -517,7 +515,7 @@ private unsafe void GetSessionInfo(SessionInfoCallback action, ref ListNextOffset && providerInstance->NextOffset < buffSize); var structBase = (byte*)providerInstance; - providerInstance = (UnsafeNativeMethods.ManifestEtw.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset]; + providerInstance = (Interop.Advapi32.TRACE_PROVIDER_INSTANCE_INFO*)&structBase[providerInstance->NextOffset]; } #else #if !ES_BUILD_PCL && PLATFORM_WINDOWS // TODO command arguments don't work on PCL builds... @@ -602,7 +600,7 @@ private static int IndexOfSessionInList(List sessions, int etwSessi /// starts, and the command being issued associated with that data. /// private unsafe bool GetDataFromController(int etwSessionId, - UnsafeNativeMethods.ManifestEtw.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart) + Interop.Advapi32.EVENT_FILTER_DESCRIPTOR* filterData, out ControllerCommand command, out byte[] data, out int dataStart) { data = null; dataStart = 0; @@ -704,18 +702,9 @@ public static WriteEventErrorCode GetLastWriteEventError() // // Helper function to set the last error on the thread // - private static void SetLastError(int error) + private static void SetLastError(WriteEventErrorCode error) { - switch (error) - { - case UnsafeNativeMethods.ManifestEtw.ERROR_ARITHMETIC_OVERFLOW: - case UnsafeNativeMethods.ManifestEtw.ERROR_MORE_DATA: - s_returnCode = WriteEventErrorCode.EventTooBig; - break; - case UnsafeNativeMethods.ManifestEtw.ERROR_NOT_ENOUGH_MEMORY: - s_returnCode = WriteEventErrorCode.NoFreeBuffers; - break; - } + s_returnCode = error; } // @@ -961,7 +950,7 @@ to fill the passed in ETW data descriptor. /// Payload for the ETW event. /// // - // + // // // // @@ -981,7 +970,7 @@ to fill the passed in ETW data descriptor. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, params object[] eventPayload) { - int status = 0; + WriteEventErrorCode status = WriteEventErrorCode.NoError; if (IsEnabled(eventDescriptor.Level, eventDescriptor.Keywords)) { @@ -1109,7 +1098,7 @@ internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr even userDataPtr[refObjPosition[7]].Ptr = (ulong)v7; } - status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData); + status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData); } } else @@ -1135,7 +1124,7 @@ internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr even } } - status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData); + status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, argCount, userData); for (int i = 0; i < refObjIndex; ++i) { @@ -1145,9 +1134,9 @@ internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr even } } - if (status != 0) + if (status != WriteEventErrorCode.NoError) { - SetLastError((int)status); + SetLastError(status); return false; } @@ -1174,7 +1163,7 @@ internal unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr even /// pointer do the event data /// // - // + // // [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] internal protected unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityID, Guid* childActivityID, int dataCount, IntPtr data) @@ -1188,7 +1177,7 @@ internal protected unsafe bool WriteEvent(ref EventDescriptor eventDescriptor, I (EventOpcode)eventDescriptor.Opcode == EventOpcode.Stop); } - int status = m_eventProvider.EventWriteTransferWrapper(m_regHandle, ref eventDescriptor, eventHandle, activityID, childActivityID, dataCount, (EventData*)data); + WriteEventErrorCode status = m_eventProvider.EventWriteTransfer(m_regHandle, in eventDescriptor, eventHandle, activityID, childActivityID, dataCount, (EventData*)data); if (status != 0) { @@ -1207,29 +1196,30 @@ internal unsafe bool WriteEventRaw( int dataCount, IntPtr data) { - int status; + WriteEventErrorCode status; - status = m_eventProvider.EventWriteTransferWrapper( + status = m_eventProvider.EventWriteTransfer( m_regHandle, - ref eventDescriptor, + in eventDescriptor, eventHandle, activityID, relatedActivityID, dataCount, (EventData*)data); - if (status != 0) + if (status != WriteEventErrorCode.NoError) { SetLastError(status); return false; } + return true; } // These are look-alikes to the Manifest based ETW OS APIs that have been shimmed to work // either with Manifest ETW or Classic ETW (if Manifest based ETW is not available). - private unsafe uint EventRegister(EventSource eventSource, UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback) + private unsafe uint EventRegister(EventSource eventSource, Interop.Advapi32.EtwEnableCallback enableCallback) { m_providerName = eventSource.Name; m_providerId = eventSource.Guid; @@ -1241,6 +1231,36 @@ private uint EventUnregister(long registrationHandle) { return m_eventProvider.EventUnregister(registrationHandle); } + +#if PLATFORM_WINDOWS + private static bool m_setInformationMissing; + + internal unsafe int SetInformation( + Interop.Advapi32.EVENT_INFO_CLASS eventInfoClass, + IntPtr data, + uint dataSize) + { + int status = Interop.Errors.ERROR_NOT_SUPPORTED; + + if (!m_setInformationMissing) + { + try + { + status = Interop.Advapi32.EventSetInformation( + m_regHandle, + eventInfoClass, + (void*)data, + (int)dataSize); + } + catch (TypeLoadException) + { + m_setInformationMissing = true; + } + } + + return status; + } +#endif } #if PLATFORM_WINDOWS @@ -1251,13 +1271,13 @@ internal sealed class EtwEventProvider : IEventProvider // Register an event provider. unsafe uint IEventProvider.EventRegister( EventSource eventSource, - UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback, + Interop.Advapi32.EtwEnableCallback enableCallback, void* callbackContext, ref long registrationHandle) { Guid providerId = eventSource.Guid; - return UnsafeNativeMethods.ManifestEtw.EventRegister( - ref providerId, + return Interop.Advapi32.EventRegister( + in providerId, enableCallback, callbackContext, ref registrationHandle); @@ -1266,32 +1286,43 @@ unsafe uint IEventProvider.EventRegister( // Unregister an event provider. uint IEventProvider.EventUnregister(long registrationHandle) { - return UnsafeNativeMethods.ManifestEtw.EventUnregister(registrationHandle); + return Interop.Advapi32.EventUnregister(registrationHandle); } // Write an event. - unsafe int IEventProvider.EventWriteTransferWrapper( + unsafe EventProvider.WriteEventErrorCode IEventProvider.EventWriteTransfer( long registrationHandle, - ref EventDescriptor eventDescriptor, + in EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityId, Guid* relatedActivityId, int userDataCount, EventProvider.EventData* userData) { - return UnsafeNativeMethods.ManifestEtw.EventWriteTransferWrapper( + int error = Interop.Advapi32.EventWriteTransfer( registrationHandle, - ref eventDescriptor, + in eventDescriptor, activityId, relatedActivityId, userDataCount, userData); + + switch (error) + { + case Interop.Errors.ERROR_ARITHMETIC_OVERFLOW: + case Interop.Errors.ERROR_MORE_DATA: + return EventProvider.WriteEventErrorCode.EventTooBig; + case Interop.Errors.ERROR_NOT_ENOUGH_MEMORY: + return EventProvider.WriteEventErrorCode.NoFreeBuffers; + } + + return EventProvider.WriteEventErrorCode.NoError; } // Get or set the per-thread activity ID. - int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId) + int IEventProvider.EventActivityIdControl(Interop.Advapi32.ActivityControl ControlCode, ref Guid ActivityId) { - return UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( + return Interop.Advapi32.EventActivityIdControl( ControlCode, ref ActivityId); } @@ -1308,7 +1339,7 @@ internal sealed class NoOpEventProvider : IEventProvider { unsafe uint IEventProvider.EventRegister( EventSource eventSource, - UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback, + Interop.Advapi32.EtwEnableCallback enableCallback, void* callbackContext, ref long registrationHandle) { @@ -1320,19 +1351,19 @@ uint IEventProvider.EventUnregister(long registrationHandle) return 0; } - unsafe int IEventProvider.EventWriteTransferWrapper( + unsafe EventProvider.WriteEventErrorCode IEventProvider.EventWriteTransfer( long registrationHandle, - ref EventDescriptor eventDescriptor, + in EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityId, Guid* relatedActivityId, int userDataCount, EventProvider.EventData* userData) { - return 0; + return EventProvider.WriteEventErrorCode.NoError; } - int IEventProvider.EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId) + int IEventProvider.EventActivityIdControl(Interop.Advapi32.ActivityControl ControlCode, ref Guid ActivityId) { return 0; } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventSource.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventSource.cs index 95ac1da80d13..50343b4c5f3d 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventSource.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/EventSource.cs @@ -132,8 +132,7 @@ // where it will write it to // // All ETW writes eventually call -// EventWriteTransfer (native PINVOKE wrapper) -// EventWriteTransferWrapper (fixes compat problem if you pass null as the related activityID) +// EventWriteTransfer // EventProvider.WriteEventRaw - sets last error // EventSource.WriteEventRaw - Does EventSource exception handling logic // WriteMultiMerge @@ -499,6 +498,123 @@ public event EventHandler EventCommandExecuted } } +#region ActivityID + + /// + /// When a thread starts work that is on behalf of 'something else' (typically another + /// thread or network request) it should mark the thread as working on that other work. + /// This API marks the current thread as working on activity 'activityID'. This API + /// should be used when the caller knows the thread's current activity (the one being + /// overwritten) has completed. Otherwise, callers should prefer the overload that + /// return the oldActivityThatWillContinue (below). + /// + /// All events created with the EventSource on this thread are also tagged with the + /// activity ID of the thread. + /// + /// It is common, and good practice after setting the thread to an activity to log an event + /// with a 'start' opcode to indicate that precise time/thread where the new activity + /// started. + /// + /// A Guid that represents the new activity with which to mark + /// the current thread + public static void SetCurrentThreadActivityId(Guid activityId) + { + if (TplEventSource.Log != null) + TplEventSource.Log.SetActivityId(activityId); + + // We ignore errors to keep with the convention that EventSources do not throw errors. + // Note we can't access m_throwOnWrites because this is a static method. +#if FEATURE_MANAGED_ETW +#if FEATURE_PERFTRACING + // Set the activity id via EventPipe. + EventPipeInternal.EventActivityIdControl( + (uint)Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID, + ref activityId); +#endif // FEATURE_PERFTRACING +#if PLATFORM_WINDOWS + // Set the activity id via ETW. + Interop.Advapi32.EventActivityIdControl( + Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID, + ref activityId); +#endif // PLATFORM_WINDOWS +#endif // FEATURE_MANAGED_ETW + } + + /// + /// Retrieves the ETW activity ID associated with the current thread. + /// + public static Guid CurrentThreadActivityId + { + get + { + // We ignore errors to keep with the convention that EventSources do not throw + // errors. Note we can't access m_throwOnWrites because this is a static method. + Guid retVal = new Guid(); +#if FEATURE_MANAGED_ETW +#if PLATFORM_WINDOWS + Interop.Advapi32.EventActivityIdControl( + Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID, + ref retVal); +#elif FEATURE_PERFTRACING + EventPipeInternal.EventActivityIdControl( + (uint)Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID, + ref retVal); +#endif // PLATFORM_WINDOWS +#endif // FEATURE_MANAGED_ETW + return retVal; + } + } + + /// + /// When a thread starts work that is on behalf of 'something else' (typically another + /// thread or network request) it should mark the thread as working on that other work. + /// This API marks the current thread as working on activity 'activityID'. It returns + /// whatever activity the thread was previously marked with. There is a convention that + /// callers can assume that callees restore this activity mark before the callee returns. + /// To encourage this, this API returns the old activity, so that it can be restored later. + /// + /// All events created with the EventSource on this thread are also tagged with the + /// activity ID of the thread. + /// + /// It is common, and good practice after setting the thread to an activity to log an event + /// with a 'start' opcode to indicate that precise time/thread where the new activity + /// started. + /// + /// A Guid that represents the new activity with which to mark + /// the current thread + /// The Guid that represents the current activity + /// which will continue at some point in the future, on the current thread + public static void SetCurrentThreadActivityId(Guid activityId, out Guid oldActivityThatWillContinue) + { + oldActivityThatWillContinue = activityId; +#if FEATURE_MANAGED_ETW + // We ignore errors to keep with the convention that EventSources do not throw errors. + // Note we can't access m_throwOnWrites because this is a static method. + +#if FEATURE_PERFTRACING && PLATFORM_WINDOWS + EventPipeInternal.EventActivityIdControl( + (uint)Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_SET_ID, + ref oldActivityThatWillContinue); +#elif FEATURE_PERFTRACING + EventPipeInternal.EventActivityIdControl( + (uint)Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref oldActivityThatWillContinue); +#endif // FEATURE_PERFTRACING && PLATFORM_WINDOWS + +#if PLATFORM_WINDOWS + Interop.Advapi32.EventActivityIdControl( + Interop.Advapi32.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, + ref oldActivityThatWillContinue); +#endif // PLATFORM_WINDOWS +#endif // FEATURE_MANAGED_ETW + + // We don't call the activityDying callback here because the caller has declared that + // it is not dying. + if (TplEventSource.Log != null) + TplEventSource.Log.SetActivityId(activityId); + } +#endregion + #region protected /// /// This is the constructor that most users will use to create their eventSource. It takes @@ -1390,7 +1506,7 @@ private unsafe void Initialize(Guid eventSourceGuid, string eventSourceName, str IntPtr providerMetadata = metadataHandle.AddrOfPinnedObject(); setInformationResult = m_etwProvider.SetInformation( - UnsafeNativeMethods.ManifestEtw.EVENT_INFO_CLASS.SetTraits, + Interop.Advapi32.EVENT_INFO_CLASS.SetTraits, providerMetadata, (uint)this.providerMetadata.Length); @@ -2321,12 +2437,40 @@ For now I'm simply marking them as public again.A cleaner solution might be to u root them and modify shared library definition to force export them. */ #if ES_BUILD_PN - public + public #else - internal + internal #endif partial struct EventMetadata { +#if ES_BUILD_PN + public EventMetadata(EventDescriptor descriptor, + EventTags tags, + bool enabledForAnyListener, + bool enabledForETW, + string name, + string message, + EventParameterType[] parameterTypes) + { + this.Descriptor = descriptor; + this.Tags = tags; + this.EnabledForAnyListener = enabledForAnyListener; + this.EnabledForETW = enabledForETW; +#if FEATURE_PERFTRACING + this.EnabledForEventPipe = false; +#endif + this.TriggersActivityTracking = 0; + this.Name = name; + this.Message = message; + this.Parameters = null; + this.TraceLoggingEventTypes = null; + this.ActivityOptions = EventActivityOptions.None; + this.ParameterTypes = parameterTypes; + this.HasRelatedActivityID = false; + this.EventHandle = IntPtr.Zero; + } +#endif + public EventDescriptor Descriptor; public IntPtr EventHandle; // EventPipeEvent handle. public EventTags Tags; @@ -2352,6 +2496,114 @@ partial struct EventMetadata #endif }; +#if !ES_BUILD_PN + private int GetParameterCount(EventMetadata eventData) + { + return eventData.Parameters.Length; + } + + private Type GetDataType(EventMetadata eventData, int parameterId) + { + return eventData.Parameters[parameterId].ParameterType; + } + + private static readonly bool m_EventSourcePreventRecursion = false; +#else + private int GetParameterCount(EventMetadata eventData) + { + int paramCount; + if(eventData.Parameters == null) + { + paramCount = eventData.ParameterTypes.Length; + } + else + { + paramCount = eventData.Parameters.Length; + } + + return paramCount; + } + + private Type GetDataType(EventMetadata eventData, int parameterId) + { + Type dataType; + if(eventData.Parameters == null) + { + dataType = EventTypeToType(eventData.ParameterTypes[parameterId]); + } + else + { + dataType = eventData.Parameters[parameterId].ParameterType; + } + + return dataType; + } + + private static readonly bool m_EventSourcePreventRecursion = true; + + public enum EventParameterType + { + Boolean, + Byte, + SByte, + Char, + Int16, + UInt16, + Int32, + UInt32, + Int64, + UInt64, + IntPtr, + Single, + Double, + Decimal, + Guid, + String + } + + private Type EventTypeToType(EventParameterType type) + { + switch (type) + { + case EventParameterType.Boolean: + return typeof(bool); + case EventParameterType.Byte: + return typeof(byte); + case EventParameterType.SByte: + return typeof(sbyte); + case EventParameterType.Char: + return typeof(char); + case EventParameterType.Int16: + return typeof(short); + case EventParameterType.UInt16: + return typeof(ushort); + case EventParameterType.Int32: + return typeof(int); + case EventParameterType.UInt32: + return typeof(uint); + case EventParameterType.Int64: + return typeof(long); + case EventParameterType.UInt64: + return typeof(ulong); + case EventParameterType.IntPtr: + return typeof(IntPtr); + case EventParameterType.Single: + return typeof(float); + case EventParameterType.Double: + return typeof(double); + case EventParameterType.Decimal: + return typeof(decimal); + case EventParameterType.Guid: + return typeof(Guid); + case EventParameterType.String: + return typeof(string); + default: + // TODO: should I throw an exception here? + return null; + } + } +#endif + // This is the internal entry point that code:EventListeners call when wanting to send a command to a // eventSource. The logic is as follows // @@ -5102,7 +5354,7 @@ public enum EventManifestOptions /// ManifestBuilder is designed to isolate the details of the message of the event from the /// rest of EventSource. This one happens to create XML. /// - internal partial class ManifestBuilder + internal class ManifestBuilder { /// /// Build a manifest for 'providerName' with the given GUID, which will be packaged into 'dllName'. @@ -5839,7 +6091,46 @@ private string GetTypeName(Type type) return typeName.Replace("win:Int", "win:UInt"); // ETW requires enums to be unsigned. } - return GetTypeNameHelper(type); + switch (type.GetTypeCode()) + { + case TypeCode.Boolean: + return "win:Boolean"; + case TypeCode.Byte: + return "win:UInt8"; + case TypeCode.Char: + case TypeCode.UInt16: + return "win:UInt16"; + case TypeCode.UInt32: + return "win:UInt32"; + case TypeCode.UInt64: + return "win:UInt64"; + case TypeCode.SByte: + return "win:Int8"; + case TypeCode.Int16: + return "win:Int16"; + case TypeCode.Int32: + return "win:Int32"; + case TypeCode.Int64: + return "win:Int64"; + case TypeCode.String: + return "win:UnicodeString"; + case TypeCode.Single: + return "win:Float"; + case TypeCode.Double: + return "win:Double"; + case TypeCode.DateTime: + return "win:FILETIME"; + default: + if (type == typeof(Guid)) + return "win:GUID"; + else if (type == typeof(IntPtr)) + return "win:Pointer"; + else if ((type.IsArray || type.IsPointer) && type.GetElementType() == typeof(byte)) + return "win:Binary"; + + ManifestError(SR.Format(SR.EventSource_UnsupportedEventTypeInManifest, type.Name), true); + return string.Empty; + } } private static void UpdateStringBuilder(ref StringBuilder stringBuilder, string eventMessage, int startIndex, int count) diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IEventProvider.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IEventProvider.cs index 9bbebc79ed23..bc7ab9aee002 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IEventProvider.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IEventProvider.cs @@ -17,7 +17,7 @@ internal interface IEventProvider // Register an event provider. unsafe uint EventRegister( EventSource eventSource, - UnsafeNativeMethods.ManifestEtw.EtwEnableCallback enableCallback, + Interop.Advapi32.EtwEnableCallback enableCallback, void* callbackContext, ref long registrationHandle); @@ -25,9 +25,9 @@ unsafe uint EventRegister( uint EventUnregister(long registrationHandle); // Write an event. - unsafe int EventWriteTransferWrapper( + unsafe EventProvider.WriteEventErrorCode EventWriteTransfer( long registrationHandle, - ref EventDescriptor eventDescriptor, + in EventDescriptor eventDescriptor, IntPtr eventHandle, Guid* activityId, Guid* relatedActivityId, @@ -35,7 +35,7 @@ unsafe int EventWriteTransferWrapper( EventProvider.EventData* userData); // Get or set the per-thread activity ID. - int EventActivityIdControl(UnsafeNativeMethods.ManifestEtw.ActivityControl ControlCode, ref Guid ActivityId); + int EventActivityIdControl(Interop.Advapi32.ActivityControl ControlCode, ref Guid ActivityId); // Define an EventPipeEvent handle. unsafe IntPtr DefineEventHandle(uint eventID, string eventName, long keywords, uint eventVersion, uint level, byte *pMetadata, uint metadataLength); diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingEventCounter.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingEventCounter.cs index 569c9a011353..40581051cc9f 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingEventCounter.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingEventCounter.cs @@ -23,7 +23,7 @@ namespace System.Diagnostics.Tracing /// It does not calculate statistics like mean, standard deviation, etc. because it only accumulates /// the counter value. /// - internal partial class IncrementingEventCounter : BaseCounter + public partial class IncrementingEventCounter : DiagnosticCounter { /// /// Initializes a new instance of the class. @@ -41,7 +41,7 @@ public IncrementingEventCounter(string name, EventSource eventSource) : base(nam /// be logged on the next timer interval. /// /// The value to increment by. - public void Increment(float increment = 1) + public void Increment(double increment = 1) { lock(MyLock) { @@ -49,25 +49,25 @@ public void Increment(float increment = 1) } } - internal TimeSpan DisplayRateTimeScale { get; set; } - private float _increment; - private float _prevIncrement; + public TimeSpan DisplayRateTimeScale { get; set; } + private double _increment; + private double _prevIncrement; - public override string ToString() => $"IncrementingEventCounter '{_name}' Increment {_increment}"; + public override string ToString() => $"IncrementingEventCounter '{Name}' Increment {_increment}"; internal override void WritePayload(float intervalSec) { lock (MyLock) // Lock the counter { IncrementingCounterPayload payload = new IncrementingCounterPayload(); - payload.Name = _name; + payload.Name = Name; payload.IntervalSec = intervalSec; payload.DisplayName = DisplayName ?? ""; payload.DisplayRateTimeScale = (DisplayRateTimeScale == TimeSpan.Zero) ? "" : DisplayRateTimeScale.ToString("c"); - payload.MetaData = GetMetaDataString(); + payload.Metadata = GetMetadataString(); payload.Increment = _increment - _prevIncrement; _prevIncrement = _increment; - _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingEventCounterPayloadType(payload)); + EventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingEventCounterPayloadType(payload)); } } } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingPollingCounter.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingPollingCounter.cs index 8bad728a1807..1b8ee7553e4d 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingPollingCounter.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/IncrementingPollingCounter.cs @@ -25,7 +25,7 @@ namespace System.Diagnostics.Tracing /// Unlike IncrementingEventCounter, this takes in a polling callback that it can call to update /// its own metric periodically. /// - internal partial class IncrementingPollingCounter : BaseCounter + public partial class IncrementingPollingCounter : DiagnosticCounter { /// /// Initializes a new instance of the class. @@ -34,20 +34,20 @@ internal partial class IncrementingPollingCounter : BaseCounter /// /// The name. /// The event source. - public IncrementingPollingCounter(string name, EventSource eventSource, Func getCountFunction) : base(name, eventSource) + public IncrementingPollingCounter(string name, EventSource eventSource, Func totalValueProvider) : base(name, eventSource) { - _getCountFunction = getCountFunction; + _totalValueProvider = totalValueProvider; } - public override string ToString() => $"IncrementingPollingCounter '{_name}' Increment {_increment}"; + public override string ToString() => $"IncrementingPollingCounter '{Name}' Increment {_increment}"; - internal TimeSpan DisplayRateTimeScale { get; set; } - private float _increment; - private float _prevIncrement; - private Func _getCountFunction; + public TimeSpan DisplayRateTimeScale { get; set; } + private double _increment; + private double _prevIncrement; + private Func _totalValueProvider; /// - /// Calls "_getCountFunction" to enqueue the counter value to the queue. + /// Calls "_totalValueProvider" to enqueue the counter value to the queue. /// private void UpdateMetric() { @@ -55,12 +55,12 @@ private void UpdateMetric() { lock(MyLock) { - _increment = _getCountFunction(); + _increment = _totalValueProvider(); } } catch (Exception ex) { - ReportOutOfBandMessage($"ERROR: Exception during EventCounter {_name} getMetricFunction callback: " + ex.Message); + ReportOutOfBandMessage($"ERROR: Exception during EventCounter {Name} getMetricFunction callback: " + ex.Message); } } @@ -70,14 +70,14 @@ internal override void WritePayload(float intervalSec) lock (MyLock) // Lock the counter { IncrementingCounterPayload payload = new IncrementingCounterPayload(); - payload.Name = _name; + payload.Name = Name; payload.DisplayName = DisplayName ?? ""; payload.DisplayRateTimeScale = (DisplayRateTimeScale == TimeSpan.Zero) ? "" : DisplayRateTimeScale.ToString("c"); payload.IntervalSec = intervalSec; - payload.MetaData = GetMetaDataString(); + payload.Metadata = GetMetadataString(); payload.Increment = _increment - _prevIncrement; _prevIncrement = _increment; - _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingPollingCounterPayloadType(payload)); + EventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new IncrementingPollingCounterPayloadType(payload)); } } } diff --git a/src/Common/src/CoreLib/System/Diagnostics/Tracing/PollingCounter.cs b/src/Common/src/CoreLib/System/Diagnostics/Tracing/PollingCounter.cs index 695b357f45a9..e0577181facc 100644 --- a/src/Common/src/CoreLib/System/Diagnostics/Tracing/PollingCounter.cs +++ b/src/Common/src/CoreLib/System/Diagnostics/Tracing/PollingCounter.cs @@ -23,7 +23,7 @@ namespace System.Diagnostics.Tracing /// function to collect metrics on its own rather than the user having to call WriteMetric() /// every time. /// - internal partial class PollingCounter : BaseCounter + public partial class PollingCounter : DiagnosticCounter { /// /// Initializes a new instance of the class. @@ -32,41 +32,42 @@ internal partial class PollingCounter : BaseCounter /// /// The name. /// The event source. - public PollingCounter(string name, EventSource eventSource, Func getMetricFunction) : base(name, eventSource) + public PollingCounter(string name, EventSource eventSource, Func metricProvider) : base(name, eventSource) { - _getMetricFunction = getMetricFunction; + _metricProvider = metricProvider; } - public override string ToString() => $"PollingCounter '{_name}' Count {1} Mean {_lastVal.ToString("n3")}"; + public override string ToString() => $"PollingCounter '{Name}' Count {1} Mean {_lastVal.ToString("n3")}"; - private Func _getMetricFunction; - private float _lastVal; + private Func _metricProvider; + private double _lastVal; internal override void WritePayload(float intervalSec) { lock (MyLock) { - float value = 0; + double value = 0; try { - value = _getMetricFunction(); + value = _metricProvider(); } catch (Exception ex) { - ReportOutOfBandMessage($"ERROR: Exception during EventCounter {_name} getMetricFunction callback: " + ex.Message); + ReportOutOfBandMessage($"ERROR: Exception during EventCounter {Name} metricProvider callback: " + ex.Message); } CounterPayload payload = new CounterPayload(); - payload.Name = _name; + payload.Name = Name; payload.DisplayName = DisplayName ?? ""; payload.Count = 1; // NOTE: These dumb-looking statistics is intentional payload.IntervalSec = intervalSec; payload.Mean = value; payload.Max = value; payload.Min = value; + payload.Metadata = GetMetadataString(); payload.StandardDeviation = 0; _lastVal = value; - _eventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new PollingPayloadType(payload)); + EventSource.Write("EventCounters", new EventSourceOptions() { Level = EventLevel.LogAlways }, new PollingPayloadType(payload)); } } } diff --git a/src/Common/src/CoreLib/System/Enum.cs b/src/Common/src/CoreLib/System/Enum.cs index 52ae2908e7c2..a979fe9a8da2 100644 --- a/src/Common/src/CoreLib/System/Enum.cs +++ b/src/Common/src/CoreLib/System/Enum.cs @@ -494,7 +494,7 @@ private static bool TryParse(string? value, bool ignoreCase, bool throwOn default: parsed = TryParseRareEnum(rt, value, valueSpan, ignoreCase, throwOnFailure, out object? objectResult); - result = parsed ? (TEnum)objectResult : default; + result = parsed ? (TEnum)objectResult! : default; // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/34976 return parsed; } } diff --git a/src/Common/src/CoreLib/System/Environment.Win32.cs b/src/Common/src/CoreLib/System/Environment.Win32.cs index f7b87ff7866b..90e1ca92a668 100644 --- a/src/Common/src/CoreLib/System/Environment.Win32.cs +++ b/src/Common/src/CoreLib/System/Environment.Win32.cs @@ -14,6 +14,8 @@ namespace System { public static partial class Environment { + internal static bool IsWindows8OrAbove => WindowsVersion.IsWindows8OrAbove; + private static string GetEnvironmentVariableFromRegistry(string variable, bool fromMachine) { Debug.Assert(variable != null); @@ -418,5 +420,35 @@ public static string GetFolderPath(SpecialFolder folder, SpecialFolderOption opt } } #endif + + // Seperate type so a .cctor is not created for Enviroment which then would be triggered during startup + private static class WindowsVersion + { + // Cache the value in readonly static that can be optimized out by the JIT + internal readonly static bool IsWindows8OrAbove = GetIsWindows8OrAbove(); + + private static bool GetIsWindows8OrAbove() + { + ulong conditionMask = Interop.Kernel32.VerSetConditionMask(0, Interop.Kernel32.VER_MAJORVERSION, Interop.Kernel32.VER_GREATER_EQUAL); + conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_MINORVERSION, Interop.Kernel32.VER_GREATER_EQUAL); + conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMAJOR, Interop.Kernel32.VER_GREATER_EQUAL); + conditionMask = Interop.Kernel32.VerSetConditionMask(conditionMask, Interop.Kernel32.VER_SERVICEPACKMINOR, Interop.Kernel32.VER_GREATER_EQUAL); + + // Windows 8 version is 6.2 + Interop.Kernel32.OSVERSIONINFOEX version = default; + unsafe + { + version.dwOSVersionInfoSize = sizeof(Interop.Kernel32.OSVERSIONINFOEX); + } + version.dwMajorVersion = 6; + version.dwMinorVersion = 2; + version.wServicePackMajor = 0; + version.wServicePackMinor = 0; + + return Interop.Kernel32.VerifyVersionInfoW(ref version, + Interop.Kernel32.VER_MAJORVERSION | Interop.Kernel32.VER_MINORVERSION | Interop.Kernel32.VER_SERVICEPACKMAJOR | Interop.Kernel32.VER_SERVICEPACKMINOR, + conditionMask); + } + } } } diff --git a/src/Common/src/CoreLib/System/Environment.WinRT.cs b/src/Common/src/CoreLib/System/Environment.WinRT.cs index a8e3dbced807..27e0fc243167 100644 --- a/src/Common/src/CoreLib/System/Environment.WinRT.cs +++ b/src/Common/src/CoreLib/System/Environment.WinRT.cs @@ -13,6 +13,8 @@ public static partial class Environment public static string UserDomainName => "Windows Domain"; + internal static readonly bool IsWindows8OrAbove = true; + private static string GetFolderPathCore(SpecialFolder folder, SpecialFolderOption option) { WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks; diff --git a/src/Common/src/CoreLib/System/GCMemoryInfo.cs b/src/Common/src/CoreLib/System/GCMemoryInfo.cs new file mode 100644 index 000000000000..72c2aca14da2 --- /dev/null +++ b/src/Common/src/CoreLib/System/GCMemoryInfo.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. +// See the LICENSE file in the project root for more information. + +namespace System +{ + public readonly struct GCMemoryInfo + { + /// + /// High memory load threshold when the last GC occured + /// + public long HighMemoryLoadThresholdBytes { get; } + + /// + /// Memory load when the last GC ocurred + /// + public long MemoryLoadBytes { get; } + + /// + /// Total available memory for the GC to use when the last GC ocurred. By default this is the physical memory on the machine, but it may be customized by specifying a HardLimit. + /// + public long TotalAvailableMemoryBytes { get; } + + /// + /// The total heap size when the last GC ocurred + /// + public long HeapSizeBytes { get; } + + /// + /// The total fragmentation when the last GC ocurred + /// + /// Let's take the example below: + /// | OBJ_A | OBJ_B | OBJ_C | OBJ_D | OBJ_E | + /// + /// Let's say OBJ_B, OBJ_C and and OBJ_E are garbage and get collected, but the heap does not get compacted, the resulting heap will look like the following: + /// | OBJ_A | F | OBJ_D | + /// + /// The memory between OBJ_A and OBJ_D marked `F` is considered part of the FragmentedBytes, and will be used to allocate new objects. The memory after OBJ_D will not be + /// considered part of the FragmentedBytes, and will also be used to allocate new objects + /// + public long FragmentedBytes { get; } + + internal GCMemoryInfo(long highMemoryLoadThresholdBytes, + long memoryLoadBytes, + long totalAvailableMemoryBytes, + long heapSizeBytes, + long fragmentedBytes) + { + HighMemoryLoadThresholdBytes = highMemoryLoadThresholdBytes; + MemoryLoadBytes = memoryLoadBytes; + TotalAvailableMemoryBytes = totalAvailableMemoryBytes; + HeapSizeBytes = heapSizeBytes; + FragmentedBytes = fragmentedBytes; + } + } +} diff --git a/src/Common/src/CoreLib/System/Gen2GcCallback.cs b/src/Common/src/CoreLib/System/Gen2GcCallback.cs index acc415b6d10d..1f8de9628e63 100644 --- a/src/Common/src/CoreLib/System/Gen2GcCallback.cs +++ b/src/Common/src/CoreLib/System/Gen2GcCallback.cs @@ -71,10 +71,7 @@ private void Setup(Func callback, object targetObj) } // Resurrect ourselves by re-registering for finalization. - if (!Environment.HasShutdownStarted) - { - GC.ReRegisterForFinalize(this); - } + GC.ReRegisterForFinalize(this); } } } diff --git a/src/Common/src/CoreLib/System/Globalization/CompareInfo.cs b/src/Common/src/CoreLib/System/Globalization/CompareInfo.cs index f5bba908b560..ef2eb4945afc 100644 --- a/src/Common/src/CoreLib/System/Globalization/CompareInfo.cs +++ b/src/Common/src/CoreLib/System/Globalization/CompareInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Text; +using System.Text.Unicode; using Internal.Runtime.CompilerServices; namespace System.Globalization diff --git a/src/Common/src/CoreLib/System/Globalization/CultureData.cs b/src/Common/src/CoreLib/System/Globalization/CultureData.cs index 7e1e66857f50..c86bab5afacb 100644 --- a/src/Common/src/CoreLib/System/Globalization/CultureData.cs +++ b/src/Common/src/CoreLib/System/Globalization/CultureData.cs @@ -11,6 +11,7 @@ namespace System.Globalization { #if CORERT + #pragma warning restore nullable using StringStringDictionary = LowLevelDictionary; using StringCultureDataDictionary = LowLevelDictionary; using LcidToCultureNameDictionary = LowLevelDictionary; diff --git a/src/Common/src/CoreLib/System/Globalization/TextInfo.cs b/src/Common/src/CoreLib/System/Globalization/TextInfo.cs index cf89dff6a2e9..4391dec044fd 100644 --- a/src/Common/src/CoreLib/System/Globalization/TextInfo.cs +++ b/src/Common/src/CoreLib/System/Globalization/TextInfo.cs @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; +using System.Text.Unicode; using Internal.Runtime.CompilerServices; #if BIT64 diff --git a/src/Common/src/CoreLib/System/IO/BinaryReader.cs b/src/Common/src/CoreLib/System/IO/BinaryReader.cs index 0636b15c9e50..273d4c7cf983 100644 --- a/src/Common/src/CoreLib/System/IO/BinaryReader.cs +++ b/src/Common/src/CoreLib/System/IO/BinaryReader.cs @@ -26,11 +26,10 @@ public class BinaryReader : IDisposable { private const int MaxCharBytesSize = 128; - private Stream _stream; - private byte[] _buffer; - private Decoder _decoder; + private readonly Stream _stream; + private readonly byte[] _buffer; + private readonly Decoder _decoder; private byte[] _charBytes; - private char[] _singleChar; private char[] _charBuffer; private int _maxCharsSize; // From MaxCharBytesSize & Encoding @@ -38,6 +37,7 @@ public class BinaryReader : IDisposable private bool _2BytesPerChar; private bool _isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf private bool _leaveOpen; + private bool _disposed; public BinaryReader(Stream input) : this(input, Encoding.UTF8, false) { @@ -95,22 +95,14 @@ public virtual Stream BaseStream protected virtual void Dispose(bool disposing) { - if (disposing) + if (!_disposed) { - Stream copyOfStream = _stream; - _stream = null; - if (copyOfStream != null && !_leaveOpen) + if (disposing && !_leaveOpen) { - copyOfStream.Close(); + _stream.Close(); } + _disposed = true; } - _isMemoryStream = false; - _stream = null; - _buffer = null; - _decoder = null; - _charBytes = null; - _singleChar = null; - _charBuffer = null; } public void Dispose() @@ -126,12 +118,17 @@ public virtual void Close() Dispose(true); } - public virtual int PeekChar() + private void ThrowIfDisposed() { - if (_stream == null) + if (_disposed) { throw Error.GetFileNotOpen(); } + } + + public virtual int PeekChar() + { + ThrowIfDisposed(); if (!_stream.CanSeek) { @@ -146,13 +143,10 @@ public virtual int PeekChar() public virtual int Read() { - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); int charsRead = 0; - int numBytes = 0; + int numBytes; long posSav = 0; if (_stream.CanSeek) @@ -164,10 +158,8 @@ public virtual int Read() { _charBytes = new byte[MaxCharBytesSize]; //REVIEW: We need at most 2 bytes/char here? } - if (_singleChar == null) - { - _singleChar = new char[1]; - } + + Span singleChar = stackalloc char[1]; while (charsRead == 0) { @@ -202,7 +194,7 @@ public virtual int Read() try { - charsRead = _decoder.GetChars(_charBytes, 0, numBytes, _singleChar, 0); + charsRead = _decoder.GetChars(new ReadOnlySpan(_charBytes, 0, numBytes), singleChar, flush: false); } catch { @@ -220,19 +212,15 @@ public virtual int Read() Debug.Assert(charsRead < 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!"); } Debug.Assert(charsRead > 0); - return _singleChar[0]; + return singleChar[0]; } public virtual byte ReadByte() => InternalReadByte(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inlined to avoid some method call overhead with InternalRead. private byte InternalReadByte() { - // Inlined to avoid some method call overhead with InternalRead. - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); int b = _stream.ReadByte(); if (b == -1) @@ -287,10 +275,7 @@ public virtual decimal ReadDecimal() public virtual string ReadString() { - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); int currPos = 0; int n; @@ -368,10 +353,7 @@ public virtual int Read(char[] buffer, int index, int count) { throw new ArgumentException(SR.Argument_InvalidOffLen); } - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); // SafeCritical: index and count have already been verified to be a valid range for the buffer return InternalReadChars(new Span(buffer, index, count)); @@ -379,17 +361,13 @@ public virtual int Read(char[] buffer, int index, int count) public virtual int Read(Span buffer) { - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } - + ThrowIfDisposed(); return InternalReadChars(buffer); } private int InternalReadChars(Span buffer) { - Debug.Assert(_stream != null); + Debug.Assert(!_disposed); int numBytes = 0; int index = 0; @@ -478,10 +456,7 @@ public virtual char[] ReadChars(int count) { throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); } - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); if (count == 0) { @@ -519,21 +494,14 @@ public virtual int Read(byte[] buffer, int index, int count) { throw new ArgumentException(SR.Argument_InvalidOffLen); } - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); return _stream.Read(buffer, index, count); } public virtual int Read(Span buffer) { - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } - + ThrowIfDisposed(); return _stream.Read(buffer); } @@ -543,10 +511,7 @@ public virtual byte[] ReadBytes(int count) { throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed); - } + ThrowIfDisposed(); if (count == 0) { @@ -584,18 +549,13 @@ private ReadOnlySpan InternalRead(int numBytes) if (_isMemoryStream) { - // no need to check if _stream == null as we will never have null _stream when _isMemoryStream = true - // read directly from MemoryStream buffer Debug.Assert(_stream is MemoryStream); return ((MemoryStream)_stream).InternalReadSpan(numBytes); } else { - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); int bytesRead = 0; int n = 0; @@ -628,10 +588,7 @@ protected virtual void FillBuffer(int numBytes) int bytesRead = 0; int n = 0; - if (_stream == null) - { - throw Error.GetFileNotOpen(); - } + ThrowIfDisposed(); // Need to find a good threshold for calling ReadByte() repeatedly // vs. calling Read(byte[], int, int) for both buffered & unbuffered diff --git a/src/Common/src/CoreLib/System/IO/BinaryWriter.cs b/src/Common/src/CoreLib/System/IO/BinaryWriter.cs index dea1f0397f11..9b6f865fe575 100644 --- a/src/Common/src/CoreLib/System/IO/BinaryWriter.cs +++ b/src/Common/src/CoreLib/System/IO/BinaryWriter.cs @@ -18,9 +18,9 @@ public class BinaryWriter : IDisposable, IAsyncDisposable public static readonly BinaryWriter Null = new BinaryWriter(); protected Stream OutStream; - private byte[] _buffer; // temp space for writing primitives to. - private Encoding _encoding; - private Encoder _encoder; + private readonly byte[] _buffer; // temp space for writing primitives to. + private readonly Encoding _encoding; + private readonly Encoder _encoder; private bool _leaveOpen; diff --git a/src/Common/src/CoreLib/System/IO/FileStream.WinRT.cs b/src/Common/src/CoreLib/System/IO/FileStream.WinRT.cs index 16b64be4ac39..752a6d9d8a96 100644 --- a/src/Common/src/CoreLib/System/IO/FileStream.WinRT.cs +++ b/src/Common/src/CoreLib/System/IO/FileStream.WinRT.cs @@ -2,10 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable using Microsoft.Win32.SafeHandles; using System.Runtime.InteropServices; -#nullable enable namespace System.IO { public partial class FileStream : Stream diff --git a/src/Common/src/CoreLib/System/IO/StreamReader.cs b/src/Common/src/CoreLib/System/IO/StreamReader.cs index 5c3cc9157d1a..bc5c00f986bb 100644 --- a/src/Common/src/CoreLib/System/IO/StreamReader.cs +++ b/src/Common/src/CoreLib/System/IO/StreamReader.cs @@ -4,7 +4,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -28,10 +27,10 @@ public class StreamReader : TextReader private const int DefaultFileStreamBufferSize = 4096; private const int MinBufferSize = 128; - private Stream _stream; + private readonly Stream _stream; private Encoding _encoding; private Decoder _decoder; - private byte[] _byteBuffer; + private readonly byte[] _byteBuffer; private char[] _charBuffer; private int _charPos; private int _charLen; @@ -45,6 +44,9 @@ public class StreamReader : TextReader // a user's char[] directly, instead of our internal char[]. private int _maxCharsPerBuffer; + /// True if the writer has been disposed; otherwise, false. + private bool _disposed; + // We will support looking for byte order marks in the stream and trying // to decide what the encoding might be from the byte order marks, IF they // exist. But that's all we'll do. @@ -90,8 +92,10 @@ private static void ThrowAsyncIOInProgress() => // The high level goal is to be tolerant of encoding errors when we read and very strict // when we write. Hence, default StreamWriter encoding will throw on error. - internal StreamReader() + private StreamReader() { + _stream = Stream.Null; + _closable = true; } public StreamReader(Stream stream) @@ -144,7 +148,23 @@ public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByt throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); } - Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen); + _stream = stream; + _encoding = encoding; + _decoder = encoding.GetDecoder(); + if (bufferSize < MinBufferSize) + { + bufferSize = MinBufferSize; + } + + _byteBuffer = new byte[bufferSize]; + _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); + _charBuffer = new char[_maxCharsPerBuffer]; + _byteLen = 0; + _bytePos = 0; + _detectEncoding = detectEncodingFromByteOrderMarks; + _checkPreamble = encoding.Preamble.Length > 0; + _isBlocked = false; + _closable = !leaveOpen; } public StreamReader(string path) @@ -167,7 +187,12 @@ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteO { } - public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) + public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) : + this(ValidateArgsAndOpenPath(path, encoding, bufferSize), encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false) + { + } + + private static Stream ValidateArgsAndOpenPath(string path, Encoding encoding, int bufferSize) { if (path == null) throw new ArgumentNullException(nameof(path)); @@ -178,37 +203,7 @@ public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteO if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, - DefaultFileStreamBufferSize, FileOptions.SequentialScan); - Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false); - } - - private void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen) - { - _stream = stream; - _encoding = encoding; - _decoder = encoding.GetDecoder(); - if (bufferSize < MinBufferSize) - { - bufferSize = MinBufferSize; - } - - _byteBuffer = new byte[bufferSize]; - _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); - _charBuffer = new char[_maxCharsPerBuffer]; - _byteLen = 0; - _bytePos = 0; - _detectEncoding = detectEncodingFromByteOrderMarks; - _checkPreamble = encoding.Preamble.Length > 0; - _isBlocked = false; - _closable = !leaveOpen; - } - - // Init used by NullStreamReader, to delay load encoding - internal void Init(Stream stream) - { - _stream = stream; - _closable = true; + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); } public override void Close() @@ -218,26 +213,26 @@ public override void Close() protected override void Dispose(bool disposing) { + if (_disposed) + { + return; + } + _disposed = true; + // Dispose of our resources if this StreamReader is closable. - // Note that Console.In should be left open. - try + if (!LeaveOpen) { - // Note that Stream.Close() can potentially throw here. So we need to - // ensure cleaning up internal resources, inside the finally block. - if (!LeaveOpen && disposing && (_stream != null)) + try { - _stream.Close(); + // Note that Stream.Close() can potentially throw here. So we need to + // ensure cleaning up internal resources, inside the finally block. + if (disposing) + { + _stream.Close(); + } } - } - finally - { - if (!LeaveOpen && (_stream != null)) + finally { - _stream = null; - _encoding = null; - _decoder = null; - _byteBuffer = null; - _charBuffer = null; _charPos = 0; _charLen = 0; base.Dispose(disposing); @@ -287,11 +282,7 @@ public bool EndOfStream { get { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (_charPos < _charLen) @@ -307,11 +298,7 @@ public bool EndOfStream public override int Peek() { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (_charPos == _charLen) @@ -326,11 +313,7 @@ public override int Peek() public override int Read() { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (_charPos == _charLen) @@ -369,11 +352,7 @@ public override int Read(Span buffer) => private int ReadSpan(Span buffer) { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); int charsRead = 0; @@ -418,11 +397,7 @@ private int ReadSpan(Span buffer) public override string ReadToEnd() { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); // Call ReadBuffer, then pull data out of charBuffer. @@ -450,11 +425,7 @@ public override int ReadBlock(char[] buffer, int index, int count) { throw new ArgumentException(SR.Argument_InvalidOffLen); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); return base.ReadBlock(buffer, index, count); @@ -800,11 +771,7 @@ private int ReadBuffer(Span userBuffer, out bool readToUserBuffer) // public override string ReadLine() { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (_charPos == _charLen) @@ -858,7 +825,6 @@ public override string ReadLine() return sb.ToString(); } - #region Task based Async APIs public override Task ReadLineAsync() { // If we have been inherited into a subclass, the following implementation could be incorrect @@ -870,11 +836,7 @@ public override Task ReadLineAsync() return base.ReadLineAsync(); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = ReadLineAsyncInternal(); @@ -958,11 +920,7 @@ public override Task ReadToEndAsync() return base.ReadToEndAsync(); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = ReadToEndAsyncInternal(); @@ -1010,11 +968,7 @@ public override Task ReadAsync(char[] buffer, int index, int count) return base.ReadAsync(buffer, index, count); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = ReadAsyncInternal(new Memory(buffer, index, count), default).AsTask(); @@ -1031,11 +985,7 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken return base.ReadAsync(buffer, cancellationToken); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (cancellationToken.IsCancellationRequested) @@ -1241,11 +1191,7 @@ public override Task ReadBlockAsync(char[] buffer, int index, int count) return base.ReadBlockAsync(buffer, index, count); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = base.ReadBlockAsync(buffer, index, count); @@ -1263,11 +1209,7 @@ public override ValueTask ReadBlockAsync(Memory buffer, CancellationT return base.ReadBlockAsync(buffer, cancellationToken); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_ReaderClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (cancellationToken.IsCancellationRequested) @@ -1359,24 +1301,21 @@ private async Task ReadBufferAsync() return _charLen; } -#endregion - - // No data, class doesn't need to be serializable. - // Note this class is threadsafe. - private class NullStreamReader : StreamReader + private void ThrowIfDisposed() { - // Instantiating Encoding causes unnecessary perf hit. - internal NullStreamReader() + if (_disposed) { - Init(Stream.Null); + ThrowObjectDisposedException(); } - public override Stream BaseStream - { - get { return Stream.Null; } - } + void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_ReaderClosed); + } + // No data, class doesn't need to be serializable. + // Note this class is threadsafe. + private sealed class NullStreamReader : StreamReader + { public override Encoding CurrentEncoding { get { return Encoding.Unicode; } diff --git a/src/Common/src/CoreLib/System/IO/StreamWriter.cs b/src/Common/src/CoreLib/System/IO/StreamWriter.cs index d55eaae582ac..8140d796480a 100644 --- a/src/Common/src/CoreLib/System/IO/StreamWriter.cs +++ b/src/Common/src/CoreLib/System/IO/StreamWriter.cs @@ -26,21 +26,20 @@ public class StreamWriter : TextWriter private const int DefaultFileStreamBufferSize = 4096; private const int MinBufferSize = 128; - private const int DontCopyOnWriteLineThreshold = 512; - // Bit bucket - Null has no backing store. Non closable. - public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, true); + public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, leaveOpen: true); - private Stream _stream; - private Encoding _encoding; - private Encoder _encoder; - private byte[] _byteBuffer; - private char[] _charBuffer; + private readonly Stream _stream; + private readonly Encoding _encoding; + private readonly Encoder _encoder; + private readonly byte[] _byteBuffer; + private readonly char[] _charBuffer; private int _charPos; private int _charLen; private bool _autoFlush; private bool _haveWrittenPreamble; - private bool _closable; + private readonly bool _closable; + private bool _disposed; // We don't guarantee thread safety on StreamWriter, but we should at // least prevent users from trying to write anything while an Async @@ -71,11 +70,6 @@ private static void ThrowAsyncIOInProgress() => // StreamReader though for different reason). Either way, the buffered data will be lost! private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM; - - internal StreamWriter() : base(null) - { // Ask for CurrentCulture all the time - } - public StreamWriter(Stream stream) : this(stream, UTF8NoBOM, DefaultBufferSize, false) { @@ -111,7 +105,25 @@ public StreamWriter(Stream stream, Encoding encoding, int bufferSize, bool leave throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); } - Init(stream, encoding, bufferSize, leaveOpen); + _stream = stream; + _encoding = encoding; + _encoder = _encoding.GetEncoder(); + if (bufferSize < MinBufferSize) + { + bufferSize = MinBufferSize; + } + + _charBuffer = new char[bufferSize]; + _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)]; + _charLen = bufferSize; + // If we're appending to a Stream that already has data, don't write + // the preamble. + if (_stream.CanSeek && _stream.Position > 0) + { + _haveWrittenPreamble = true; + } + + _closable = !leaveOpen; } public StreamWriter(string path) @@ -129,8 +141,13 @@ public StreamWriter(string path, bool append, Encoding encoding) { } - public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) + public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) : + this(ValidateArgsAndOpenPath(path, append, encoding, bufferSize), encoding, bufferSize, leaveOpen: false) { + } + + private static Stream ValidateArgsAndOpenPath(string path, bool append, Encoding encoding, int bufferSize) + { if (path == null) throw new ArgumentNullException(nameof(path)); if (encoding == null) @@ -140,32 +157,7 @@ public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) if (bufferSize <= 0) throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - Stream stream = new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, - DefaultFileStreamBufferSize, FileOptions.SequentialScan); - Init(stream, encoding, bufferSize, shouldLeaveOpen: false); - } - - private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen) - { - _stream = streamArg; - _encoding = encodingArg; - _encoder = _encoding.GetEncoder(); - if (bufferSize < MinBufferSize) - { - bufferSize = MinBufferSize; - } - - _charBuffer = new char[bufferSize]; - _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)]; - _charLen = bufferSize; - // If we're appending to a Stream that already has data, don't write - // the preamble. - if (_stream.CanSeek && _stream.Position > 0) - { - _haveWrittenPreamble = true; - } - - _closable = !shouldLeaveOpen; + return new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); } public override void Close() @@ -182,15 +174,11 @@ protected override void Dispose(bool disposing) // Also, we never close the handles for stdout & friends. So we can safely // write any buffered data to those streams even during finalization, which // is generally the right thing to do. - if (_stream != null) + if (!_disposed && disposing) { // Note: flush on the underlying stream can throw (ex., low disk space) - if (disposing /* || (LeaveOpen && stream is __ConsoleStream) */) - { - CheckAsyncTaskInProgress(); - - Flush(true, true); - } + CheckAsyncTaskInProgress(); + Flush(flushStream: true, flushEncoder: true); } } finally @@ -202,7 +190,7 @@ protected override void Dispose(bool disposing) private void CloseStreamFromDispose(bool disposing) { // Dispose of our resources if this StreamWriter is closable. - if (!LeaveOpen && _stream != null) + if (!LeaveOpen && !_disposed) { try { @@ -217,11 +205,7 @@ private void CloseStreamFromDispose(bool disposing) } finally { - _stream = null; - _byteBuffer = null; - _charBuffer = null; - _encoding = null; - _encoder = null; + _disposed = true; _charLen = 0; base.Dispose(disposing); } @@ -239,7 +223,7 @@ private async ValueTask DisposeAsyncCore() Debug.Assert(GetType() == typeof(StreamWriter)); try { - if (_stream != null) + if (!_disposed) { await FlushAsync().ConfigureAwait(false); } @@ -263,11 +247,8 @@ private void Flush(bool flushStream, bool flushEncoder) // flushEncoder should be true at the end of the file and if // the user explicitly calls Flush (though not if AutoFlush is true). // This is required to flush any dangling characters from our UTF-7 - // and UTF-8 encoders. - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } + // and UTF-8 encoders. + ThrowIfDisposed(); // Perf boost for Flush on non-dirty writers. if (_charPos == 0 && !flushStream && !flushEncoder) @@ -420,11 +401,8 @@ private unsafe void WriteSpan(ReadOnlySpan buffer, bool appendNewLine) // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also // make local copies of instance state to protect against potential concurrent misuse. + ThrowIfDisposed(); char[] charBuffer = _charBuffer; - if (charBuffer == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer)) fixed (char* dstPtr = &charBuffer[0]) @@ -620,7 +598,6 @@ public override void WriteLine(string format, params object[] arg) } } - #region Task based Async APIs public override Task WriteAsync(char value) { // If we have been inherited into a subclass, the following implementation could be incorrect @@ -632,11 +609,7 @@ public override Task WriteAsync(char value) return base.WriteAsync(value); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false); @@ -701,11 +674,7 @@ public override Task WriteAsync(string value) if (value != null) { - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false); @@ -809,11 +778,7 @@ public override Task WriteAsync(char[] buffer, int index, int count) return base.WriteAsync(buffer, index, count); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, new ReadOnlyMemory(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default); @@ -830,11 +795,7 @@ public override Task WriteAsync(ReadOnlyMemory buffer, CancellationToken c return base.WriteAsync(buffer, cancellationToken); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (cancellationToken.IsCancellationRequested) @@ -909,11 +870,7 @@ public override Task WriteLineAsync() return base.WriteLineAsync(); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, ReadOnlyMemory.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default); @@ -934,11 +891,7 @@ public override Task WriteLineAsync(char value) return base.WriteLineAsync(value); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true); @@ -964,11 +917,7 @@ public override Task WriteLineAsync(string value) return base.WriteLineAsync(value); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true); @@ -1006,11 +955,7 @@ public override Task WriteLineAsync(char[] buffer, int index, int count) return base.WriteLineAsync(buffer, index, count); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = WriteAsyncInternal(this, new ReadOnlyMemory(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default); @@ -1026,11 +971,7 @@ public override Task WriteLineAsync(ReadOnlyMemory buffer, CancellationTok return base.WriteLineAsync(buffer, cancellationToken); } - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); if (cancellationToken.IsCancellationRequested) @@ -1060,11 +1001,7 @@ public override Task FlushAsync() // the user explicitly calls Flush (though not if AutoFlush is true). // This is required to flush any dangling characters from our UTF-7 // and UTF-8 encoders. - if (_stream == null) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_WriterClosed); - } - + ThrowIfDisposed(); CheckAsyncTaskInProgress(); Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos); @@ -1135,6 +1072,15 @@ private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStrea await stream.FlushAsync(cancellationToken).ConfigureAwait(false); } } - #endregion + + private void ThrowIfDisposed() + { + if (_disposed) + { + ThrowObjectDisposedException(); + } + + void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed); + } } // class StreamWriter } // namespace diff --git a/src/Common/src/CoreLib/System/Marvin.OrdinalIgnoreCase.cs b/src/Common/src/CoreLib/System/Marvin.OrdinalIgnoreCase.cs index beab0cfe02e8..9e9bb3162304 100644 --- a/src/Common/src/CoreLib/System/Marvin.OrdinalIgnoreCase.cs +++ b/src/Common/src/CoreLib/System/Marvin.OrdinalIgnoreCase.cs @@ -5,7 +5,7 @@ using System.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; +using System.Text.Unicode; using Internal.Runtime.CompilerServices; #if BIT64 diff --git a/src/Common/src/CoreLib/System/Math.cs b/src/Common/src/CoreLib/System/Math.cs index 1c36c9d23071..ac9be486734c 100644 --- a/src/Common/src/CoreLib/System/Math.cs +++ b/src/Common/src/CoreLib/System/Math.cs @@ -538,31 +538,23 @@ public static decimal Max(decimal val1, decimal val2) public static double Max(double val1, double val2) { - // When val1 and val2 are both finite or infinite, return the larger - // * We count +0.0 as larger than -0.0 to match MSVC - // When val1 or val2, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `maximum` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the larger of the inputs. It + // treats +0 as larger than -0 as per the specification. - if (double.IsNaN(val1)) - { - return val2; - } - - if (double.IsNaN(val2)) + if ((val1 > val2) || double.IsNaN(val1)) { return val1; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (val1 < val2) first could get transformed into (val2 >= val1) by the JIT - // which would then return an incorrect value - if (val1 == val2) { return double.IsNegative(val1) ? val2 : val1; } - return (val1 < val2) ? val2 : val1; + return val2; } [NonVersionable] @@ -592,31 +584,23 @@ public static sbyte Max(sbyte val1, sbyte val2) public static float Max(float val1, float val2) { - // When val1 and val2 are both finite or infinite, return the larger - // * We count +0.0 as larger than -0.0 to match MSVC - // When val1 or val2, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `maximum` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the larger of the inputs. It + // treats +0 as larger than -0 as per the specification. - if (float.IsNaN(val1)) - { - return val2; - } - - if (float.IsNaN(val2)) + if ((val1 > val2) || float.IsNaN(val1)) { return val1; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (val1 < val2) first could get transformed into (val2 >= val1) by the JIT - // which would then return an incorrect value - if (val1 == val2) { return float.IsNegative(val1) ? val2 : val1; } - return (val1 < val2) ? val2 : val1; + return val2; } [CLSCompliant(false)] @@ -642,34 +626,26 @@ public static ulong Max(ulong val1, ulong val2) public static double MaxMagnitude(double x, double y) { - // When x and y are both finite or infinite, return the larger magnitude - // * We count +0.0 as larger than -0.0 to match MSVC - // When x or y, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `maximumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. - if (double.IsNaN(x)) - { - return y; - } + double ax = Abs(x); + double ay = Abs(y); - if (double.IsNaN(y)) + if ((ax > ay) || double.IsNaN(ax)) { return x; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (ax < ay) first could get transformed into (ay >= ax) by the JIT which would - // then return an incorrect value - - double ax = Abs(x); - double ay = Abs(y); - if (ax == ay) { return double.IsNegative(x) ? y : x; } - return (ax < ay) ? y : x; + return y; } [NonVersionable] @@ -686,31 +662,23 @@ public static decimal Min(decimal val1, decimal val2) public static double Min(double val1, double val2) { - // When val1 and val2 are both finite or infinite, return the smaller - // * We count -0.0 as smaller than -0.0 to match MSVC - // When val1 or val2, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `minimum` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the larger of the inputs. It + // treats +0 as larger than -0 as per the specification. - if (double.IsNaN(val1)) - { - return val2; - } - - if (double.IsNaN(val2)) + if ((val1 < val2) || double.IsNaN(val1)) { return val1; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (val1 < val2) first could get transformed into (val2 >= val1) by the JIT - // which would then return an incorrect value - if (val1 == val2) { return double.IsNegative(val1) ? val1 : val2; } - return (val1 < val2) ? val1 : val2; + return val2; } [NonVersionable] @@ -740,31 +708,23 @@ public static sbyte Min(sbyte val1, sbyte val2) public static float Min(float val1, float val2) { - // When val1 and val2 are both finite or infinite, return the smaller - // * We count -0.0 as smaller than -0.0 to match MSVC - // When val1 or val2, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `minimum` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the larger of the inputs. It + // treats +0 as larger than -0 as per the specification. - if (float.IsNaN(val1)) - { - return val2; - } - - if (float.IsNaN(val2)) + if ((val1 < val2) || float.IsNaN(val1)) { return val1; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (val1 < val2) first could get transformed into (val2 >= val1) by the JIT - // which would then return an incorrect value - if (val1 == val2) { return float.IsNegative(val1) ? val1 : val2; } - return (val1 < val2) ? val1 : val2; + return val2; } [CLSCompliant(false)] @@ -790,34 +750,26 @@ public static ulong Min(ulong val1, ulong val2) public static double MinMagnitude(double x, double y) { - // When x and y are both finite or infinite, return the smaller magnitude - // * We count -0.0 as smaller than -0.0 to match MSVC - // When x or y, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `minimumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. - if (double.IsNaN(x)) - { - return y; - } + double ax = Abs(x); + double ay = Abs(y); - if (double.IsNaN(y)) + if ((ax < ay) || double.IsNaN(ax)) { return x; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (ax < ay) first could get transformed into (ay >= ax) by the JIT which would - // then return an incorrect value - - double ax = Abs(x); - double ay = Abs(y); - if (ax == ay) { return double.IsNegative(x) ? x : y; } - return (ax < ay) ? x : y; + return y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Common/src/CoreLib/System/MathF.cs b/src/Common/src/CoreLib/System/MathF.cs index 1defa4e40baa..bf324866ec75 100644 --- a/src/Common/src/CoreLib/System/MathF.cs +++ b/src/Common/src/CoreLib/System/MathF.cs @@ -190,34 +190,26 @@ public static float Max(float x, float y) public static float MaxMagnitude(float x, float y) { - // When x and y are both finite or infinite, return the larger magnitude - // * We count +0.0 as larger than -0.0 to match MSVC - // When x or y, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `maximumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. - if (float.IsNaN(x)) - { - return y; - } + float ax = Abs(x); + float ay = Abs(y); - if (float.IsNaN(y)) + if ((ax > ay) || float.IsNaN(ax)) { return x; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (ax < ay) first could get transformed into (ay >= ax) by the JIT which would - // then return an incorrect value - - float ax = Abs(x); - float ay = Abs(y); - if (ax == ay) { return float.IsNegative(x) ? y : x; } - return (ax < ay) ? y : x; + return y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -228,34 +220,26 @@ public static float Min(float x, float y) public static float MinMagnitude(float x, float y) { - // When x and y are both finite or infinite, return the smaller magnitude - // * We count -0.0 as smaller than -0.0 to match MSVC - // When x or y, but not both, are NaN return the opposite - // * We return the opposite if either is NaN to match MSVC + // This matches the IEEE 754:2019 `minimumMagnitude` function + // + // It propagates NaN inputs back to the caller and + // otherwise returns the input with a larger magnitude. + // It treats +0 as larger than -0 as per the specification. - if (float.IsNaN(x)) - { - return y; - } + float ax = Abs(x); + float ay = Abs(y); - if (float.IsNaN(y)) + if ((ax < ay) || float.IsNaN(ax)) { return x; } - // We do this comparison first and separately to handle the -0.0 to +0.0 comparision - // * Doing (ax < ay) first could get transformed into (ay >= ax) by the JIT which would - // then return an incorrect value - - float ax = Abs(x); - float ay = Abs(y); - if (ax == ay) { return float.IsNegative(x) ? x : y; } - return (ax < ay) ? x : y; + return y; } [Intrinsic] diff --git a/src/Common/src/CoreLib/System/Memory.cs b/src/Common/src/CoreLib/System/Memory.cs index d468a8ca2470..e3d0347f11a8 100644 --- a/src/Common/src/CoreLib/System/Memory.cs +++ b/src/Common/src/CoreLib/System/Memory.cs @@ -12,6 +12,14 @@ using Internal.Runtime.CompilerServices; +#if BIT64 +using nint = System.Int64; +using nuint = System.UInt64; +#else // BIT64 +using nint = System.Int32; +using nuint = System.UInt32; +#endif // BIT64 + namespace System { /// @@ -273,35 +281,6 @@ public Memory Slice(int start, int length) return new Memory(_object, _index + start, length); } - /// - /// Forms a slice out of the given memory, beginning at 'startIndex' - /// - /// The index at which to begin this slice. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory Slice(Index startIndex) - { - int actualIndex = startIndex.GetOffset(_length); - return Slice(actualIndex); - } - - /// - /// Forms a slice out of the given memory using the range start and end indexes. - /// - /// The range used to slice the memory using its start and end indexes. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Memory Slice(Range range) - { - (int start, int length) = range.GetOffsetAndLength(_length); - // It is expected for _index + start to be negative if the memory is already pre-pinned. - return new Memory(_object, _index + start, length); - } - - /// - /// Forms a slice out of the given memory using the range start and end indexes. - /// - /// The range used to slice the memory using its start and end indexes. - public Memory this[Range range] => Slice(range); - /// /// Returns a span from the memory. /// @@ -372,12 +351,12 @@ public unsafe Span Span // least to be in-bounds when compared with the original Memory instance, so using the span won't // AV the process. - int desiredStartIndex = _index & ReadOnlyMemory.RemoveFlagsBitMask; + nuint desiredStartIndex = (uint)_index & (uint)ReadOnlyMemory.RemoveFlagsBitMask; int desiredLength = _length; #if BIT64 // See comment in Span.Slice for how this works. - if ((ulong)(uint)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan) + if ((ulong)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan) { ThrowHelper.ThrowArgumentOutOfRangeException(); } @@ -388,7 +367,7 @@ public unsafe Span Span } #endif - refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex); + refToReturn = ref Unsafe.Add(ref refToReturn, (IntPtr)(void*)desiredStartIndex); lengthOfUnderlyingSpan = desiredLength; } diff --git a/src/Common/src/CoreLib/System/MemoryExtensions.Trim.cs b/src/Common/src/CoreLib/System/MemoryExtensions.Trim.cs index be98bb1fba7f..96581c7c0183 100644 --- a/src/Common/src/CoreLib/System/MemoryExtensions.Trim.cs +++ b/src/Common/src/CoreLib/System/MemoryExtensions.Trim.cs @@ -18,7 +18,7 @@ public static Memory Trim(this Memory memory, T trimElement) { ReadOnlySpan span = memory.Span; int start = ClampStart(span, trimElement); - int length = ClampEnd(span, start + 1, trimElement); + int length = ClampEnd(span, start, trimElement); return memory.Slice(start, length); } @@ -50,7 +50,7 @@ public static ReadOnlyMemory Trim(this ReadOnlyMemory memory, T trimEle { ReadOnlySpan span = memory.Span; int start = ClampStart(span, trimElement); - int length = ClampEnd(span, start + 1, trimElement); + int length = ClampEnd(span, start, trimElement); return memory.Slice(start, length); } @@ -81,7 +81,7 @@ public static Span Trim(this Span span, T trimElement) where T : IEquatable { int start = ClampStart(span, trimElement); - int length = ClampEnd(span, start + 1, trimElement); + int length = ClampEnd(span, start, trimElement); return span.Slice(start, length); } @@ -112,7 +112,7 @@ public static ReadOnlySpan Trim(this ReadOnlySpan span, T trimElement) where T : IEquatable { int start = ClampStart(span, trimElement); - int length = ClampEnd(span, start + 1, trimElement); + int length = ClampEnd(span, start, trimElement); return span.Slice(start, length); } @@ -220,7 +220,7 @@ public static Memory Trim(this Memory memory, ReadOnlySpan trimEleme { ReadOnlySpan span = memory.Span; int start = ClampStart(span, trimElements); - int length = ClampEnd(span, start + 1, trimElements); + int length = ClampEnd(span, start, trimElements); return memory.Slice(start, length); } @@ -292,7 +292,7 @@ public static ReadOnlyMemory Trim(this ReadOnlyMemory memory, ReadOnlyS { ReadOnlySpan span = memory.Span; int start = ClampStart(span, trimElements); - int length = ClampEnd(span, start + 1, trimElements); + int length = ClampEnd(span, start, trimElements); return memory.Slice(start, length); } @@ -364,7 +364,7 @@ public static Span Trim(this Span span, ReadOnlySpan trimElements) if (trimElements.Length > 1) { int start = ClampStart(span, trimElements); - int length = ClampEnd(span, start + 1, trimElements); + int length = ClampEnd(span, start, trimElements); return span.Slice(start, length); } @@ -435,7 +435,7 @@ public static ReadOnlySpan Trim(this ReadOnlySpan span, ReadOnlySpan if (trimElements.Length > 1) { int start = ClampStart(span, trimElements); - int length = ClampEnd(span, start + 1, trimElements); + int length = ClampEnd(span, start, trimElements); return span.Slice(start, length); } @@ -547,7 +547,7 @@ public static Memory Trim(this Memory memory) { ReadOnlySpan span = memory.Span; int start = ClampStart(span); - int length = ClampEnd(span, start + 1); + int length = ClampEnd(span, start); return memory.Slice(start, length); } @@ -573,7 +573,7 @@ public static ReadOnlyMemory Trim(this ReadOnlyMemory memory) { ReadOnlySpan span = memory.Span; int start = ClampStart(span); - int length = ClampEnd(span, start + 1); + int length = ClampEnd(span, start); return memory.Slice(start, length); } @@ -803,7 +803,7 @@ public static ReadOnlySpan TrimEnd(this ReadOnlySpan span, ReadOnlyS public static Span Trim(this Span span) { int start = ClampStart(span); - int length = ClampEnd(span, start + 1); + int length = ClampEnd(span, start); return span.Slice(start, length); } diff --git a/src/Common/src/CoreLib/System/ObjectDisposedException.cs b/src/Common/src/CoreLib/System/ObjectDisposedException.cs index d5d488447f9c..450a9597e34a 100644 --- a/src/Common/src/CoreLib/System/ObjectDisposedException.cs +++ b/src/Common/src/CoreLib/System/ObjectDisposedException.cs @@ -4,16 +4,16 @@ #nullable enable using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace System { - /// - /// The exception that is thrown when accessing an object that was - /// disposed. - /// + /// + /// The exception that is thrown when accessing an object that was disposed. + /// [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] + [TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] public class ObjectDisposedException : InvalidOperationException { private string? _objectName; @@ -53,16 +53,18 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont info.AddValue("ObjectName", ObjectName, typeof(string)); } - /// - /// Gets the text for the message for this exception. - /// + /// + /// Gets the text for the message for this exception. + /// public override string Message { get { string name = ObjectName; if (string.IsNullOrEmpty(name)) + { return base.Message; + } string objectDisposed = SR.Format(SR.ObjectDisposed_ObjectName_Name, name); return base.Message + Environment.NewLine + objectDisposed; diff --git a/src/Common/src/CoreLib/System/Range.cs b/src/Common/src/CoreLib/System/Range.cs index 0098dea17ff7..fc5ec5242343 100644 --- a/src/Common/src/CoreLib/System/Range.cs +++ b/src/Common/src/CoreLib/System/Range.cs @@ -95,15 +95,15 @@ public override string ToString() /// Create a Range object starting from first element to the end. public static Range All => new Range(Index.Start, Index.End); - /// Destruct the range object according to a collection length and return the start offset from the beginning and the length of this range. - /// The length of the collection that the range will be used with. length has to be a positive value + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. /// /// For performance reason, we don't validate the input length parameter against negative values. /// It is expected Range will be used with collections which always have non negative length/count. /// We validate the range is inside the length scope though. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public OffsetAndLength GetOffsetAndLength(int length) + public (int Offset, int Length) GetOffsetAndLength(int length) { int start; Index startIndex = Start; @@ -124,25 +124,7 @@ public OffsetAndLength GetOffsetAndLength(int length) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); } - return new OffsetAndLength(start, end - start); - } - - public readonly struct OffsetAndLength - { - public int Offset { get; } - public int Length { get; } - - public OffsetAndLength(int offset, int length) - { - Offset = offset; - Length = length; - } - - public void Deconstruct(out int offset, out int length) - { - offset = Offset; - length = Length; - } + return (start, end - start); } } } diff --git a/src/Common/src/CoreLib/System/ReadOnlyMemory.cs b/src/Common/src/CoreLib/System/ReadOnlyMemory.cs index be664e62325a..67c85eb2b383 100644 --- a/src/Common/src/CoreLib/System/ReadOnlyMemory.cs +++ b/src/Common/src/CoreLib/System/ReadOnlyMemory.cs @@ -12,6 +12,14 @@ using Internal.Runtime.CompilerServices; +#if BIT64 +using nint = System.Int64; +using nuint = System.UInt64; +#else // BIT64 +using nint = System.Int32; +using nuint = System.UInt32; +#endif // BIT64 + namespace System { /// @@ -202,35 +210,6 @@ public ReadOnlyMemory Slice(int start, int length) return new ReadOnlyMemory(_object, _index + start, length); } - /// - /// Forms a slice out of the given memory, beginning at 'startIndex' - /// - /// The index at which to begin this slice. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory Slice(Index startIndex) - { - int actualIndex = startIndex.GetOffset(_length); - return Slice(actualIndex); - } - - /// - /// Forms a slice out of the given memory using the range start and end indexes. - /// - /// The range used to slice the memory using its start and end indexes. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlyMemory Slice(Range range) - { - (int start, int length) = range.GetOffsetAndLength(_length); - // It is expected for _index + start to be negative if the memory is already pre-pinned. - return new ReadOnlyMemory(_object, _index + start, length); - } - - /// - /// Forms a slice out of the given memory using the range start and end indexes. - /// - /// The range used to slice the memory using its start and end indexes. - public ReadOnlyMemory this[Range range] => Slice(range); - /// /// Returns a span from the memory. /// @@ -294,12 +273,12 @@ public unsafe ReadOnlySpan Span // least to be in-bounds when compared with the original Memory instance, so using the span won't // AV the process. - int desiredStartIndex = _index & RemoveFlagsBitMask; + nuint desiredStartIndex = (uint)_index & (uint)RemoveFlagsBitMask; int desiredLength = _length; #if BIT64 // See comment in Span.Slice for how this works. - if ((ulong)(uint)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan) + if ((ulong)desiredStartIndex + (ulong)(uint)desiredLength > (ulong)(uint)lengthOfUnderlyingSpan) { ThrowHelper.ThrowArgumentOutOfRangeException(); } @@ -310,7 +289,7 @@ public unsafe ReadOnlySpan Span } #endif - refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex); + refToReturn = ref Unsafe.Add(ref refToReturn, (IntPtr)(void*)desiredStartIndex); lengthOfUnderlyingSpan = desiredLength; } diff --git a/src/Common/src/CoreLib/System/ReadOnlySpan.Fast.cs b/src/Common/src/CoreLib/System/ReadOnlySpan.Fast.cs index 00337a5fd7f5..df4933766130 100644 --- a/src/Common/src/CoreLib/System/ReadOnlySpan.Fast.cs +++ b/src/Common/src/CoreLib/System/ReadOnlySpan.Fast.cs @@ -153,18 +153,6 @@ public ref readonly T this[int index] #endif } - public ref readonly T this[Index index] - { - get - { - // Evaluate the actual index first because it helps performance - int actualIndex = index.GetOffset(_length); - return ref this [actualIndex]; - } - } - - public ReadOnlySpan this[Range range] => Slice(range); - /// /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference. /// It can be used for pinning and is required to support the use of span within a fixed statement. @@ -292,33 +280,6 @@ public ReadOnlySpan Slice(int start, int length) return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); } - /// - /// Forms a slice out of the given read-only span, beginning at 'startIndex' - /// - /// The index at which to begin this slice. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan Slice(Index startIndex) - { - int actualIndex; - if (startIndex.IsFromEnd) - actualIndex = _length - startIndex.Value; - else - actualIndex = startIndex.Value; - - return Slice(actualIndex); - } - - /// - /// Forms a slice out of the given read-only span, beginning at range start index to the range end - /// - /// The range which has the start and end indexes used to slice the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan Slice(Range range) - { - (int start, int length) = range.GetOffsetAndLength(_length); - return new ReadOnlySpan(ref Unsafe.Add(ref _pointer.Value, start), length); - } - /// /// Copies the contents of this read-only span into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes diff --git a/src/Common/src/CoreLib/System/Reflection/Assembly.cs b/src/Common/src/CoreLib/System/Reflection/Assembly.cs index 3db428ed7937..1e9699f62f27 100644 --- a/src/Common/src/CoreLib/System/Reflection/Assembly.cs +++ b/src/Common/src/CoreLib/System/Reflection/Assembly.cs @@ -205,7 +205,17 @@ public static Assembly LoadWithPartialName(string partialName) if (partialName == null) throw new ArgumentNullException(nameof(partialName)); - return Load(partialName); + if ((partialName.Length == 0) || (partialName[0] == '\0')) + throw new ArgumentException(SR.Format_StringZeroLength, nameof(partialName)); + + try + { + return Load(partialName); + } + catch (FileNotFoundException) + { + return null; + } } // Loads the assembly with a COFF based IMAGE containing diff --git a/src/Common/src/CoreLib/System/Reflection/AssemblyName.cs b/src/Common/src/CoreLib/System/Reflection/AssemblyName.cs new file mode 100644 index 000000000000..5acf5cf2a82f --- /dev/null +++ b/src/Common/src/CoreLib/System/Reflection/AssemblyName.cs @@ -0,0 +1,498 @@ +// 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.Configuration.Assemblies; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using CultureInfo = System.Globalization.CultureInfo; + +namespace System.Reflection +{ + public sealed partial class AssemblyName : ICloneable, IDeserializationCallback, ISerializable + { + // If you modify any of these fields, you must also update the + // AssemblyBaseObject structure in object.h + private string _name; + private byte[] _publicKey; + private byte[] _publicKeyToken; + private CultureInfo _cultureInfo; + private string _codeBase; + private Version _version; + + private StrongNameKeyPair _strongNameKeyPair; + + private AssemblyHashAlgorithm _hashAlgorithm; + + private AssemblyVersionCompatibility _versionCompatibility; + private AssemblyNameFlags _flags; + + public AssemblyName() + { + _versionCompatibility = AssemblyVersionCompatibility.SameMachine; + } + + // Set and get the name of the assembly. If this is a weak Name + // then it optionally contains a site. For strong assembly names, + // the name partitions up the strong name's namespace + public string Name + { + get { return _name; } + set { _name = value; } + } + + public Version Version + { + get { return _version; } + set { _version = value; } + } + + // Locales, internally the LCID is used for the match. + public CultureInfo CultureInfo + { + get { return _cultureInfo; } + set { _cultureInfo = value; } + } + + public string CultureName + { + get + { + return (_cultureInfo == null) ? null : _cultureInfo.Name; + } + set + { + _cultureInfo = (value == null) ? null : new CultureInfo(value); + } + } + + public string CodeBase + { + get { return _codeBase; } + set { _codeBase = value; } + } + + public string EscapedCodeBase + { + get + { + if (_codeBase == null) + return null; + else + return EscapeCodeBase(_codeBase); + } + } + + public ProcessorArchitecture ProcessorArchitecture + { + get + { + int x = (((int)_flags) & 0x70) >> 4; + if (x > 5) + x = 0; + return (ProcessorArchitecture)x; + } + set + { + int x = ((int)value) & 0x07; + if (x <= 5) + { + _flags = (AssemblyNameFlags)((int)_flags & 0xFFFFFF0F); + _flags |= (AssemblyNameFlags)(x << 4); + } + } + } + + public AssemblyContentType ContentType + { + get + { + int x = (((int)_flags) & 0x00000E00) >> 9; + if (x > 1) + x = 0; + return (AssemblyContentType)x; + } + set + { + int x = ((int)value) & 0x07; + if (x <= 1) + { + _flags = (AssemblyNameFlags)((int)_flags & 0xFFFFF1FF); + _flags |= (AssemblyNameFlags)(x << 9); + } + } + } + + // Make a copy of this assembly name. + public object Clone() + { + var name = new AssemblyName + { + _name = _name, + _publicKey = (byte[])_publicKey?.Clone(), + _publicKeyToken = (byte[])_publicKeyToken?.Clone(), + _cultureInfo = _cultureInfo, + _version = (Version)_version?.Clone(), + _flags = _flags, + _codeBase = _codeBase, + _hashAlgorithm = _hashAlgorithm, + _versionCompatibility = _versionCompatibility, + }; + return name; + } + + /* + * Get the AssemblyName for a given file. This will only work + * if the file contains an assembly manifest. This method causes + * the file to be opened and closed. + */ + public static AssemblyName GetAssemblyName(string assemblyFile) + { + if (assemblyFile == null) + throw new ArgumentNullException(nameof(assemblyFile)); + + return GetFileInformationCore(assemblyFile); + } + + public byte[] GetPublicKey() + { + return _publicKey; + } + + public void SetPublicKey(byte[] publicKey) + { + _publicKey = publicKey; + + if (publicKey == null) + _flags &= ~AssemblyNameFlags.PublicKey; + else + _flags |= AssemblyNameFlags.PublicKey; + } + + // The compressed version of the public key formed from a truncated hash. + // Will throw a SecurityException if _publicKey is invalid + public byte[] GetPublicKeyToken() + { + if (_publicKeyToken == null) + _publicKeyToken = ComputePublicKeyToken(); + return _publicKeyToken; + } + + public void SetPublicKeyToken(byte[] publicKeyToken) + { + _publicKeyToken = publicKeyToken; + } + + // Flags modifying the name. So far the only flag is PublicKey, which + // indicates that a full public key and not the compressed version is + // present. + // Processor Architecture flags are set only through ProcessorArchitecture + // property and can't be set or retrieved directly + // Content Type flags are set only through ContentType property and can't be + // set or retrieved directly + public AssemblyNameFlags Flags + { + get { return (AssemblyNameFlags)((uint)_flags & 0xFFFFF10F); } + set + { + _flags &= unchecked((AssemblyNameFlags)0x00000EF0); + _flags |= (value & unchecked((AssemblyNameFlags)0xFFFFF10F)); + } + } + + public AssemblyHashAlgorithm HashAlgorithm + { + get { return _hashAlgorithm; } + set { _hashAlgorithm = value; } + } + + public AssemblyVersionCompatibility VersionCompatibility + { + get { return _versionCompatibility; } + set { _versionCompatibility = value; } + } + + public StrongNameKeyPair KeyPair + { + get { return _strongNameKeyPair; } + set { _strongNameKeyPair = value; } + } + + public string FullName + { + get + { + if (this.Name == null) + return string.Empty; + // Do not call GetPublicKeyToken() here - that latches the result into AssemblyName which isn't a side effect we want. + byte[] pkt = _publicKeyToken ?? ComputePublicKeyToken(); + return AssemblyNameFormatter.ComputeDisplayName(Name, Version, CultureName, pkt, Flags, ContentType); + } + } + + public override string ToString() + { + string s = FullName; + if (s == null) + return base.ToString(); + else + return s; + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + throw new PlatformNotSupportedException(); + } + + public void OnDeserialization(object sender) + { + throw new PlatformNotSupportedException(); + } + + /// + /// Compares the simple names disregarding Version, Culture and PKT. While this clearly does not + /// match the intent of this api, this api has been broken this way since its debut and we cannot + /// change its behavior now. + /// + public static bool ReferenceMatchesDefinition(AssemblyName reference, AssemblyName definition) + { + if (object.ReferenceEquals(reference, definition)) + return true; + + if (reference == null) + throw new ArgumentNullException(nameof(reference)); + + if (definition == null) + throw new ArgumentNullException(nameof(definition)); + + string refName = reference.Name ?? string.Empty; + string defName = definition.Name ?? string.Empty; + return refName.Equals(defName, StringComparison.OrdinalIgnoreCase); + } + + internal static string EscapeCodeBase(string codebase) + { + if (codebase == null) + return string.Empty; + + int position = 0; + char[] dest = EscapeString(codebase, 0, codebase.Length, null, ref position, true, c_DummyChar, c_DummyChar, c_DummyChar); + if (dest == null) + return codebase; + + return new string(dest, 0, position); + } + + // This implementation of EscapeString has been copied from System.Private.Uri from corefx repo + // - forceX characters are always escaped if found + // - rsvd character will remain unescaped + // + // start - starting offset from input + // end - the exclusive ending offset in input + // 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 + // + internal static unsafe char[] EscapeString(string input, int start, int end, char[] dest, ref int destPos, + bool isUriString, char force1, char force2, char rsvd) + { + int i = start; + int prevInputPos = start; + byte* bytes = stackalloc byte[c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar]; // 40*4=160 + + fixed (char* pStr = input) + { + for (; i < end; ++i) + { + char ch = pStr[i]; + + // a Unicode ? + if (ch > '\x7F') + { + short maxSize = (short)Math.Min(end - i, (int)c_MaxUnicodeCharsReallocate - 1); + + short count = 1; + for (; count < maxSize && pStr[i + count] > '\x7f'; ++count) + ; + + // Is the last a high surrogate? + if (pStr[i + count - 1] >= 0xD800 && pStr[i + count - 1] <= 0xDBFF) + { + // Should be a rare case where the app tries to feed an invalid Unicode surrogates pair + if (count == 1 || count == end - i) + throw new FormatException(SR.Arg_FormatException); + // need to grab one more char as a Surrogate except when it's a bogus input + ++count; + } + + dest = EnsureDestinationSize(pStr, dest, i, + (short)(count * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte), + c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar * c_EncodedCharsPerByte, + ref destPos, prevInputPos); + + short numberOfBytes = (short)Encoding.UTF8.GetBytes(pStr + i, count, bytes, + c_MaxUnicodeCharsReallocate * c_MaxUTF_8BytesPerUnicodeChar); + + // This is the only exception that built in UriParser can throw after a Uri ctor. + // Should not happen unless the app tries to feed an invalid Unicode string + if (numberOfBytes == 0) + throw new FormatException(SR.Arg_FormatException); + + i += (count - 1); + + for (count = 0; count < numberOfBytes; ++count) + EscapeAsciiChar((char)bytes[count], dest, ref destPos); + + prevInputPos = i + 1; + } + else if (ch == '%' && rsvd == '%') + { + // Means we don't reEncode '%' but check for the possible escaped sequence + dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte, + c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos); + if (i + 2 < end && EscapedAscii(pStr[i + 1], pStr[i + 2]) != c_DummyChar) + { + // leave it escaped + dest[destPos++] = '%'; + dest[destPos++] = pStr[i + 1]; + dest[destPos++] = pStr[i + 2]; + i += 2; + } + else + { + EscapeAsciiChar('%', dest, ref destPos); + } + prevInputPos = i + 1; + } + else if (ch == force1 || ch == force2) + { + dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte, + c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos); + EscapeAsciiChar(ch, dest, ref destPos); + prevInputPos = i + 1; + } + else if (ch != rsvd && (isUriString ? !IsReservedUnreservedOrHash(ch) : !IsUnreserved(ch))) + { + dest = EnsureDestinationSize(pStr, dest, i, c_EncodedCharsPerByte, + c_MaxAsciiCharsReallocate * c_EncodedCharsPerByte, ref destPos, prevInputPos); + EscapeAsciiChar(ch, dest, ref destPos); + prevInputPos = i + 1; + } + } + + if (prevInputPos != i) + { + // need to fill up the dest array ? + if (prevInputPos != start || dest != null) + dest = EnsureDestinationSize(pStr, dest, i, 0, 0, ref destPos, prevInputPos); + } + } + + return dest; + } + + // + // ensure destination array has enough space and contains all the needed input stuff + // + private static unsafe char[] EnsureDestinationSize(char* pStr, char[] dest, int currentInputPos, + short charsToAdd, short minReallocateChars, ref int destPos, int prevInputPos) + { + if ((object)dest == null || dest.Length < destPos + (currentInputPos - prevInputPos) + charsToAdd) + { + // allocating or reallocating array by ensuring enough space based on maxCharsToAdd. + char[] newresult = new char[destPos + (currentInputPos - prevInputPos) + minReallocateChars]; + + if ((object)dest != null && destPos != 0) + Buffer.BlockCopy(dest, 0, newresult, 0, destPos << 1); + dest = newresult; + } + + // ensuring we copied everything form the input string left before last escaping + while (prevInputPos != currentInputPos) + dest[destPos++] = pStr[prevInputPos++]; + return dest; + } + + internal static void EscapeAsciiChar(char ch, char[] to, ref int pos) + { + to[pos++] = '%'; + to[pos++] = s_hexUpperChars[(ch & 0xf0) >> 4]; + to[pos++] = s_hexUpperChars[ch & 0xf]; + } + + internal static char EscapedAscii(char digit, char next) + { + if (!(((digit >= '0') && (digit <= '9')) + || ((digit >= 'A') && (digit <= 'F')) + || ((digit >= 'a') && (digit <= 'f')))) + { + return c_DummyChar; + } + + int res = (digit <= '9') + ? ((int)digit - (int)'0') + : (((digit <= 'F') + ? ((int)digit - (int)'A') + : ((int)digit - (int)'a')) + + 10); + + if (!(((next >= '0') && (next <= '9')) + || ((next >= 'A') && (next <= 'F')) + || ((next >= 'a') && (next <= 'f')))) + { + return c_DummyChar; + } + + return (char)((res << 4) + ((next <= '9') + ? ((int)next - (int)'0') + : (((next <= 'F') + ? ((int)next - (int)'A') + : ((int)next - (int)'a')) + + 10))); + } + + private static bool IsReservedUnreservedOrHash(char c) + { + if (IsUnreserved(c)) + { + return true; + } + return (RFC3986ReservedMarks.Contains(c)); + } + + internal static bool IsUnreserved(char c) + { + if (IsAsciiLetterOrDigit(c)) + { + return true; + } + return (RFC3986UnreservedMarks.Contains(c)); + } + + //Only consider ASCII characters + internal static bool IsAsciiLetter(char character) + { + return (character >= 'a' && character <= 'z') || + (character >= 'A' && character <= 'Z'); + } + + internal static bool IsAsciiLetterOrDigit(char character) + { + return IsAsciiLetter(character) || (character >= '0' && character <= '9'); + } + + private static readonly char[] s_hexUpperChars = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + internal const char c_DummyChar = (char)0xFFFF; //An Invalid Unicode character used as a dummy char passed into the parameter + private const short c_MaxAsciiCharsReallocate = 40; + private const short c_MaxUnicodeCharsReallocate = 40; + private const short c_MaxUTF_8BytesPerUnicodeChar = 4; + private const short c_EncodedCharsPerByte = 3; + private const string RFC3986ReservedMarks = @":/?#[]@!$&'()*+,;="; + private const string RFC3986UnreservedMarks = @"-._~"; + } +} diff --git a/src/Common/src/CoreLib/System/Resources/ResourceSet.cs b/src/Common/src/CoreLib/System/Resources/ResourceSet.cs index 2d33f3a8b086..b076f29617b0 100644 --- a/src/Common/src/CoreLib/System/Resources/ResourceSet.cs +++ b/src/Common/src/CoreLib/System/Resources/ResourceSet.cs @@ -36,7 +36,7 @@ protected ResourceSet() { // To not inconvenience people subclassing us, we should allocate a new // hashtable here just so that Table is set to something. - CommonInit(); + Table = new Hashtable(); } // For RuntimeResourceSet, ignore the Table parameter - it's a wasted @@ -50,9 +50,9 @@ internal ResourceSet(bool junk) // on disk. // public ResourceSet(string fileName) + : this() { Reader = new ResourceReader(fileName); - CommonInit(); ReadResources(); } @@ -61,26 +61,21 @@ public ResourceSet(string fileName) // of data. // public ResourceSet(Stream stream) + : this() { Reader = new ResourceReader(stream); - CommonInit(); ReadResources(); } public ResourceSet(IResourceReader reader) + : this() { if (reader == null) throw new ArgumentNullException(nameof(reader)); Reader = reader; - CommonInit(); ReadResources(); } - private void CommonInit() - { - Table = new Hashtable(); - } - // Closes and releases any resources used by this ResourceSet, if any. // All calls to methods on the ResourceSet after a call to close may // fail. Close is guaranteed to be safely callable multiple times on a diff --git a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConditionalWeakTable.cs b/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConditionalWeakTable.cs index a7ea972b4113..505134a5cc20 100644 --- a/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConditionalWeakTable.cs +++ b/src/Common/src/CoreLib/System/Runtime/CompilerServices/ConditionalWeakTable.cs @@ -750,11 +750,9 @@ private void VerifyIntegrity() ~Container() { - // We're just freeing per-appdomain unmanaged handles here. If we're already shutting down the AD, - // don't bother. (Despite its name, Environment.HasShutdownStart also returns true if the current - // AD is finalizing.) We also skip doing anything if the container is invalid, including if someone + // Skip doing anything if the container is invalid, including if somehow // the container object was allocated but its associated table never set. - if (Environment.HasShutdownStarted || _invalid || _parent is null) + if (_invalid || _parent is null) { return; } diff --git a/src/Common/src/CoreLib/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/Common/src/CoreLib/System/Runtime/CompilerServices/RuntimeHelpers.cs index fcb69eceecef..6925d97b9b16 100644 --- a/src/Common/src/CoreLib/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/Common/src/CoreLib/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Runtime.Serialization; +using Internal.Runtime.CompilerServices; namespace System.Runtime.CompilerServices { @@ -13,22 +14,39 @@ public static partial class RuntimeHelpers public delegate void CleanupCode(object userData, bool exceptionThrown); /// - /// GetSubArray helper method for the compiler to slice an array using a range. + /// Slices the specified array using the specified range. /// public static T[] GetSubArray(T[] array, Range range) { - Type elementType = array.GetType().GetElementType(); - Span source = array.AsSpan(range); + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + (int offset, int length) = range.GetOffsetAndLength(array.Length); - if (elementType.IsValueType) + if (default(T) != null || typeof(T[]) == array.GetType()) { - return source.ToArray(); + // We know the type of the array to be exactly T[]. + + if (length == 0) + { + return Array.Empty(); + } + + var dest = new T[length]; + Buffer.Memmove( + ref Unsafe.As(ref dest.GetRawSzArrayData()), + ref Unsafe.Add(ref Unsafe.As(ref array.GetRawSzArrayData()), offset), + (uint)length); + return dest; } else { - T[] newArray = (T[])Array.CreateInstance(elementType, source.Length); - source.CopyTo(newArray); - return newArray; + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length); + Array.Copy(array, offset, dest, 0, length); + return dest; } } @@ -38,7 +56,7 @@ public static object GetUninitializedObject(Type type) { throw new ArgumentNullException(nameof(type), SR.ArgumentNull_Type); } - + if (!type.IsRuntimeImplemented()) { throw new SerializationException(SR.Format(SR.Serialization_InvalidType, type.ToString())); @@ -63,4 +81,4 @@ public static void PrepareConstrainedRegionsNoOP() { } } -} \ No newline at end of file +} diff --git a/src/Common/src/CoreLib/System/Runtime/InteropServices/Marshal.cs b/src/Common/src/CoreLib/System/Runtime/InteropServices/Marshal.cs index 99ab1010c73f..65d4b4fc6a4b 100644 --- a/src/Common/src/CoreLib/System/Runtime/InteropServices/Marshal.cs +++ b/src/Common/src/CoreLib/System/Runtime/InteropServices/Marshal.cs @@ -57,7 +57,7 @@ public static unsafe string PtrToStringAnsi(IntPtr ptr, int len) } if (len < 0) { - throw new ArgumentException(null, nameof(len)); + throw new ArgumentOutOfRangeException(nameof(len), len, SR.ArgumentOutOfRange_NeedNonNegNum); } return new string((sbyte*)ptr, 0, len); @@ -81,7 +81,7 @@ public static unsafe string PtrToStringUni(IntPtr ptr, int len) } if (len < 0) { - throw new ArgumentException(SR.ArgumentOutOfRange_NeedNonNegNum, nameof(len)); + throw new ArgumentOutOfRangeException(nameof(len), len, SR.ArgumentOutOfRange_NeedNonNegNum); } return new string((char*)ptr, 0, len); @@ -89,7 +89,7 @@ public static unsafe string PtrToStringUni(IntPtr ptr, int len) public static unsafe string PtrToStringUTF8(IntPtr ptr) { - if (ptr == IntPtr.Zero) + if (ptr == IntPtr.Zero || IsWin32Atom(ptr)) { return null; } @@ -100,14 +100,13 @@ public static unsafe string PtrToStringUTF8(IntPtr ptr) public static unsafe string PtrToStringUTF8(IntPtr ptr, int byteLen) { - if (byteLen < 0) + if (ptr == IntPtr.Zero) { - throw new ArgumentOutOfRangeException(nameof(byteLen), SR.ArgumentOutOfRange_NeedNonNegNum); + throw new ArgumentNullException(nameof(ptr)); } - - if (ptr == IntPtr.Zero || IsWin32Atom(ptr)) + if (byteLen < 0) { - return null; + throw new ArgumentOutOfRangeException(nameof(byteLen), byteLen, SR.ArgumentOutOfRange_NeedNonNegNum); } return string.CreateStringFromEncoding((byte*)ptr, byteLen, Encoding.UTF8); diff --git a/src/Common/src/CoreLib/System/Runtime/InteropServices/MarshalAsAttribute.cs b/src/Common/src/CoreLib/System/Runtime/InteropServices/MarshalAsAttribute.cs index 4a64050ed109..816a4627e599 100644 --- a/src/Common/src/CoreLib/System/Runtime/InteropServices/MarshalAsAttribute.cs +++ b/src/Common/src/CoreLib/System/Runtime/InteropServices/MarshalAsAttribute.cs @@ -5,7 +5,7 @@ namespace System.Runtime.InteropServices { [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.ReturnValue, Inherited = false)] - public sealed class MarshalAsAttribute : Attribute + public sealed partial class MarshalAsAttribute : Attribute { public MarshalAsAttribute(UnmanagedType unmanagedType) { diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Aes.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Aes.PlatformNotSupported.cs index ba9c310a6359..086589389f3a 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Aes.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Aes.PlatformNotSupported.cs @@ -17,7 +17,8 @@ namespace System.Runtime.Intrinsics.Arm.Arm64 [CLSCompliant(false)] public static class Aes { - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } + /// /// Performs AES single round decryption /// vaesdq_u8 (uint8x16_t data, uint8x16_t key) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Base.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Base.PlatformNotSupported.cs index a74e23829f54..99fabb0a6a97 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Base.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Base.PlatformNotSupported.cs @@ -16,7 +16,7 @@ namespace System.Runtime.Intrinsics.Arm.Arm64 [CLSCompliant(false)] public static class Base { - public static bool IsSupported { get { return false; }} + public static bool IsSupported { [Intrinsic] get { return false; }} /// /// Vector LeadingSignCount diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha1.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha1.PlatformNotSupported.cs index 16a73c639364..61474ea3826f 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha1.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha1.PlatformNotSupported.cs @@ -17,8 +17,7 @@ namespace System.Runtime.Intrinsics.Arm.Arm64 [CLSCompliant(false)] public static class Sha1 { - - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// Performs SHA1 hash update choose form. diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha256.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha256.PlatformNotSupported.cs index f56cfa4597d9..2fbaf05eda84 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha256.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Sha256.PlatformNotSupported.cs @@ -17,7 +17,7 @@ namespace System.Runtime.Intrinsics.Arm.Arm64 [CLSCompliant(false)] public static class Sha256 { - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// Performs SHA256 hash update (part 1). diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Simd.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Simd.PlatformNotSupported.cs index cefa1ed37ce8..e7184b5739f6 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Simd.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Arm/Arm64/Simd.PlatformNotSupported.cs @@ -21,7 +21,7 @@ public static class Simd /// IsSupported property indicates whether any method provided /// by this class is supported by the current runtime. /// - public static bool IsSupported { get { return false; }} + public static bool IsSupported { [Intrinsic] get { return false; }} /// /// Vector abs diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Vector256_1.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Vector256_1.cs index 903d2cd9410c..e0ab5c28bfec 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/Vector256_1.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/Vector256_1.cs @@ -105,13 +105,13 @@ public bool Equals(Vector256 other) { if (typeof(T) == typeof(float)) { - Vector256 result = Avx.Compare(this.AsSingle(), other.AsSingle(), FloatComparisonMode.EqualOrderedNonSignaling); + Vector256 result = Avx.Compare(this.AsSingle(), other.AsSingle(), FloatComparisonMode.OrderedEqualNonSignaling); return Avx.MoveMask(result) == 0b1111_1111; // We have one bit per element } if (typeof(T) == typeof(double)) { - Vector256 result = Avx.Compare(this.AsDouble(), other.AsDouble(), FloatComparisonMode.EqualOrderedNonSignaling); + Vector256 result = Avx.Compare(this.AsDouble(), other.AsDouble(), FloatComparisonMode.OrderedEqualNonSignaling); return Avx.MoveMask(result) == 0b1111; // We have one bit per element } } diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Aes.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Aes.PlatformNotSupported.cs index 1a0dc5e8f0fa..3d261d7c6698 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Aes.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Aes.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Aes : Sse2 { internal Aes() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m128i _mm_aesdec_si128 (__m128i a, __m128i RoundKey) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx.PlatformNotSupported.cs index 97d545dc3b92..a873913579d9 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Avx : Sse42 { internal Avx() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m256 _mm256_add_ps (__m256 a, __m256 b) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.PlatformNotSupported.cs index ad2244b7b1c3..2696c866b799 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Avx2 : Avx { internal Avx2() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m256i _mm256_abs_epi8 (__m256i a) @@ -673,64 +674,125 @@ internal Avx2() { } /// /// __m256i _mm256_cvtepi8_epi16 (__m128i a) - /// VPMOVSXBW ymm, xmm/m128 + /// VPMOVSXBW ymm, xmm /// public static Vector256 ConvertToVector256Int16(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m256i _mm256_cvtepu8_epi16 (__m128i a) - /// VPMOVZXBW ymm, xmm/m128 + /// VPMOVZXBW ymm, xmm /// - public static Vector256 ConvertToVector256UInt16(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector256 ConvertToVector256Int16(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m256i _mm256_cvtepi8_epi32 (__m128i a) - /// VPMOVSXBD ymm, xmm/m128 + /// VPMOVSXBD ymm, xmm /// public static Vector256 ConvertToVector256Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// - /// __m256i _mm256_cvtepi16_epi32 (__m128i a) - /// VPMOVSXWD ymm, xmm/m128 + /// __m256i _mm256_cvtepu8_epi32 (__m128i a) + /// VPMOVZXBD ymm, xmm /// - public static Vector256 ConvertToVector256Int32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector256 ConvertToVector256Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// - /// __m256i _mm256_cvtepu8_epi32 (__m128i a) - /// VPMOVZXBD ymm, xmm/m128 + /// __m256i _mm256_cvtepi16_epi32 (__m128i a) + /// VPMOVSXWD ymm, xmm /// - public static Vector256 ConvertToVector256UInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector256 ConvertToVector256Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m256i _mm256_cvtepu16_epi32 (__m128i a) - /// VPMOVZXWD ymm, xmm/m128 + /// VPMOVZXWD ymm, xmm /// - public static Vector256 ConvertToVector256UInt32(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector256 ConvertToVector256Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m256i _mm256_cvtepi8_epi64 (__m128i a) - /// VPMOVSXBQ ymm, xmm/m128 + /// VPMOVSXBQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// + /// __m256i _mm256_cvtepu8_epi64 (__m128i a) + /// VPMOVZXBQ ymm, xmm + /// + public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } + /// /// __m256i _mm256_cvtepi16_epi64 (__m128i a) - /// VPMOVSXWQ ymm, xmm/m128 + /// VPMOVSXWQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// + /// __m256i _mm256_cvtepu16_epi64 (__m128i a) + /// VPMOVZXWQ ymm, xmm + /// + public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } + /// /// __m256i _mm256_cvtepi32_epi64 (__m128i a) - /// VPMOVSXDQ ymm, xmm/m128 + /// VPMOVSXDQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// - /// __m256i _mm256_cvtepu8_epi64 (__m128i a) - /// VPMOVZXBQ ymm, xmm/m128 + /// __m256i _mm256_cvtepu32_epi64 (__m128i a) + /// VPMOVZXDQ ymm, xmm /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) { throw new PlatformNotSupportedException(); } + public static Vector256 ConvertToVector256Int64(Vector128 value) { throw new PlatformNotSupportedException(); } + /// - /// __m256i _mm256_cvtepu16_epi64 (__m128i a) - /// VPMOVZXWQ ymm, xmm/m128 + /// VPMOVSXBW ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) { throw new PlatformNotSupportedException(); } + public static unsafe Vector256 ConvertToVector256Int16(sbyte* address) { throw new PlatformNotSupportedException(); } /// - /// __m256i _mm256_cvtepu32_epi64 (__m128i a) - /// VPMOVZXDQ ymm, xmm/m128 + /// VPMOVZXBW ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int16(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVSXBD ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(sbyte* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVZXBD ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVSXWD ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(short* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVZXWD ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(ushort* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVSXBQ ymm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(sbyte* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVZXBQ ymm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVSXWQ ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(short* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVZXWQ ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(ushort* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVSXDQ ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(int* address) { throw new PlatformNotSupportedException(); } + /// + /// VPMOVZXDQ ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) { throw new PlatformNotSupportedException(); } + public static unsafe Vector256 ConvertToVector256Int64(uint* address) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm256_extracti128_si256 (__m256i a, const int imm8) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.cs index 33b101b96211..9b6f2a61622c 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Avx2.cs @@ -674,64 +674,125 @@ internal Avx2() { } /// /// __m256i _mm256_cvtepi8_epi16 (__m128i a) - /// VPMOVSXBW ymm, xmm/m128 + /// VPMOVSXBW ymm, xmm /// public static Vector256 ConvertToVector256Int16(Vector128 value) => ConvertToVector256Int16(value); /// /// __m256i _mm256_cvtepu8_epi16 (__m128i a) - /// VPMOVZXBW ymm, xmm/m128 + /// VPMOVZXBW ymm, xmm /// - public static Vector256 ConvertToVector256UInt16(Vector128 value) => ConvertToVector256UInt16(value); + public static Vector256 ConvertToVector256Int16(Vector128 value) => ConvertToVector256Int16(value); /// /// __m256i _mm256_cvtepi8_epi32 (__m128i a) - /// VPMOVSXBD ymm, xmm/m128 + /// VPMOVSXBD ymm, xmm /// public static Vector256 ConvertToVector256Int32(Vector128 value) => ConvertToVector256Int32(value); /// - /// __m256i _mm256_cvtepi16_epi32 (__m128i a) - /// VPMOVSXWD ymm, xmm/m128 + /// __m256i _mm256_cvtepu8_epi32 (__m128i a) + /// VPMOVZXBD ymm, xmm /// - public static Vector256 ConvertToVector256Int32(Vector128 value) => ConvertToVector256Int32(value); + public static Vector256 ConvertToVector256Int32(Vector128 value) => ConvertToVector256Int32(value); /// - /// __m256i _mm256_cvtepu8_epi32 (__m128i a) - /// VPMOVZXBD ymm, xmm/m128 + /// __m256i _mm256_cvtepi16_epi32 (__m128i a) + /// VPMOVSXWD ymm, xmm /// - public static Vector256 ConvertToVector256UInt32(Vector128 value) => ConvertToVector256UInt32(value); + public static Vector256 ConvertToVector256Int32(Vector128 value) => ConvertToVector256Int32(value); /// /// __m256i _mm256_cvtepu16_epi32 (__m128i a) - /// VPMOVZXWD ymm, xmm/m128 + /// VPMOVZXWD ymm, xmm /// - public static Vector256 ConvertToVector256UInt32(Vector128 value) => ConvertToVector256UInt32(value); + public static Vector256 ConvertToVector256Int32(Vector128 value) => ConvertToVector256Int32(value); /// /// __m256i _mm256_cvtepi8_epi64 (__m128i a) - /// VPMOVSXBQ ymm, xmm/m128 + /// VPMOVSXBQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); /// + /// __m256i _mm256_cvtepu8_epi64 (__m128i a) + /// VPMOVZXBQ ymm, xmm + /// + public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); + /// /// __m256i _mm256_cvtepi16_epi64 (__m128i a) - /// VPMOVSXWQ ymm, xmm/m128 + /// VPMOVSXWQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); /// + /// __m256i _mm256_cvtepu16_epi64 (__m128i a) + /// VPMOVZXWQ ymm, xmm + /// + public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); + /// /// __m256i _mm256_cvtepi32_epi64 (__m128i a) - /// VPMOVSXDQ ymm, xmm/m128 + /// VPMOVSXDQ ymm, xmm /// public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); /// - /// __m256i _mm256_cvtepu8_epi64 (__m128i a) - /// VPMOVZXBQ ymm, xmm/m128 + /// __m256i _mm256_cvtepu32_epi64 (__m128i a) + /// VPMOVZXDQ ymm, xmm /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) => ConvertToVector256UInt64(value); + public static Vector256 ConvertToVector256Int64(Vector128 value) => ConvertToVector256Int64(value); + /// - /// __m256i _mm256_cvtepu16_epi64 (__m128i a) - /// VPMOVZXWQ ymm, xmm/m128 + /// VPMOVSXBW ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) => ConvertToVector256UInt64(value); + public static unsafe Vector256 ConvertToVector256Int16(sbyte* address) => ConvertToVector256Int16(address); /// - /// __m256i _mm256_cvtepu32_epi64 (__m128i a) - /// VPMOVZXDQ ymm, xmm/m128 + /// VPMOVZXBW ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int16(byte* address) => ConvertToVector256Int16(address); + /// + /// VPMOVSXBD ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(sbyte* address) => ConvertToVector256Int32(address); + /// + /// VPMOVZXBD ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(byte* address) => ConvertToVector256Int32(address); + /// + /// VPMOVSXWD ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(short* address) => ConvertToVector256Int32(address); + /// + /// VPMOVZXWD ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int32(ushort* address) => ConvertToVector256Int32(address); + /// + /// VPMOVSXBQ ymm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(sbyte* address) => ConvertToVector256Int64(address); + /// + /// VPMOVZXBQ ymm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(byte* address) => ConvertToVector256Int64(address); + /// + /// VPMOVSXWQ ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(short* address) => ConvertToVector256Int64(address); + /// + /// VPMOVZXWQ ymm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(ushort* address) => ConvertToVector256Int64(address); + /// + /// VPMOVSXDQ ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector256 ConvertToVector256Int64(int* address) => ConvertToVector256Int64(address); + /// + /// VPMOVZXDQ ymm, m128 + /// The native signature does not exist. We provide this additional overload for completeness. /// - public static Vector256 ConvertToVector256UInt64(Vector128 value) => ConvertToVector256UInt64(value); + public static unsafe Vector256 ConvertToVector256Int64(uint* address) => ConvertToVector256Int64(address); /// /// __m128i _mm256_extracti128_si256 (__m256i a, const int imm8) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi1.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi1.PlatformNotSupported.cs index 5281097e3bfd..62c0dfd89414 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi1.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi1.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Bmi1 { internal Bmi1() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } public abstract class X64 { internal X64() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// unsigned __int64 _andn_u64 (unsigned __int64 a, unsigned __int64 b) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi2.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi2.PlatformNotSupported.cs index 7c32e712ae73..b78494e9c73b 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi2.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Bmi2.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Bmi2 { internal Bmi2() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } public abstract class X64 { internal X64() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// unsigned __int64 _bzhi_u64 (unsigned __int64 a, unsigned int index) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Enums.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Enums.cs index 5dbbbed88f53..80aa680cc6e3 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Enums.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Enums.cs @@ -10,17 +10,17 @@ public enum FloatComparisonMode : byte /// /// _CMP_EQ_OQ /// - EqualOrderedNonSignaling = 0, + OrderedEqualNonSignaling = 0, /// /// _CMP_LT_OS /// - LessThanOrderedSignaling = 1, + OrderedLessThanSignaling = 1, /// /// _CMP_LE_OS /// - LessThanOrEqualOrderedSignaling = 2, + OrderedLessThanOrEqualSignaling = 2, /// /// _CMP_UNORD_Q @@ -30,17 +30,17 @@ public enum FloatComparisonMode : byte /// /// _CMP_NEQ_UQ /// - NotEqualUnorderedNonSignaling = 4, + UnorderedNotEqualNonSignaling = 4, /// /// _CMP_NLT_US /// - NotLessThanUnorderedSignaling = 5, + UnorderedNotLessThanSignaling = 5, /// /// _CMP_NLE_US /// - NotLessThanOrEqualUnorderedSignaling = 6, + UnorderedNotLessThanOrEqualSignaling = 6, /// /// _CMP_ORD_Q @@ -50,57 +50,57 @@ public enum FloatComparisonMode : byte /// /// _CMP_EQ_UQ /// - EqualUnorderedNonSignaling = 8, + UnorderedEqualNonSignaling = 8, /// /// _CMP_NGE_US /// - NotGreaterThanOrEqualUnorderedSignaling = 9, + UnorderedNotGreaterThanOrEqualSignaling = 9, /// /// _CMP_NGT_US /// - NotGreaterThanUnorderedSignaling = 10, + UnorderedNotGreaterThanSignaling = 10, /// /// _CMP_FALSE_OQ /// - FalseOrderedNonSignaling = 11, + OrderedFalseNonSignaling = 11, /// /// _CMP_NEQ_OQ /// - NotEqualOrderedNonSignaling = 12, + OrderedNotEqualNonSignaling = 12, /// /// _CMP_GE_OS /// - GreaterThanOrEqualOrderedSignaling = 13, + OrderedGreaterThanOrEqualSignaling = 13, /// /// _CMP_GT_OS /// - GreaterThanOrderedSignaling = 14, + OrderedGreaterThanSignaling = 14, /// /// _CMP_TRUE_UQ /// - TrueUnorderedNonSignaling = 15, + UnorderedTrueNonSignaling = 15, /// /// _CMP_EQ_OS /// - EqualOrderedSignaling = 16, + OrderedEqualSignaling = 16, /// /// _CMP_LT_OQ /// - LessThanOrderedNonSignaling = 17, + OrderedLessThanNonSignaling = 17, /// /// _CMP_LE_OQ /// - LessThanOrEqualOrderedNonSignaling = 18, + OrderedLessThanOrEqualNonSignaling = 18, /// /// _CMP_UNORD_S @@ -110,17 +110,17 @@ public enum FloatComparisonMode : byte /// /// _CMP_NEQ_US /// - NotEqualUnorderedSignaling = 20, + UnorderedNotEqualSignaling = 20, /// /// _CMP_NLT_UQ /// - NotLessThanUnorderedNonSignaling = 21, + UnorderedNotLessThanNonSignaling = 21, /// /// _CMP_NLE_UQ /// - NotLessThanOrEqualUnorderedNonSignaling = 22, + UnorderedNotLessThanOrEqualNonSignaling = 22, /// /// _CMP_ORD_S @@ -130,41 +130,41 @@ public enum FloatComparisonMode : byte /// /// _CMP_EQ_US /// - EqualUnorderedSignaling = 24, + UnorderedEqualSignaling = 24, /// /// _CMP_NGE_UQ /// - NotGreaterThanOrEqualUnorderedNonSignaling = 25, + UnorderedNotGreaterThanOrEqualNonSignaling = 25, /// /// _CMP_NGT_UQ /// - NotGreaterThanUnorderedNonSignaling = 26, + UnorderedNotGreaterThanNonSignaling = 26, /// /// _CMP_FALSE_OS /// - FalseOrderedSignaling = 27, + OrderedFalseSignaling = 27, /// /// _CMP_NEQ_OS /// - NotEqualOrderedSignaling = 28, + OrderedNotEqualSignaling = 28, /// /// _CMP_GE_OQ /// - GreaterThanOrEqualOrderedNonSignaling = 29, + OrderedGreaterThanOrEqualNonSignaling = 29, /// /// _CMP_GT_OQ /// - GreaterThanOrderedNonSignaling = 30, + OrderedGreaterThanNonSignaling = 30, /// /// _CMP_TRUE_US /// - TrueUnorderedSignaling = 31, + UnorderedTrueSignaling = 31, } } diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Fma.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Fma.PlatformNotSupported.cs index 6d9c2931d4b2..760aa40a1753 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Fma.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Fma.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Fma : Avx { internal Fma() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m128 _mm_fmadd_ps (__m128 a, __m128 b, __m128 c) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Lzcnt.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Lzcnt.PlatformNotSupported.cs index cdf23928f9b4..ce0086506321 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Lzcnt.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Lzcnt.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; namespace System.Runtime.Intrinsics.X86 { @@ -15,13 +16,13 @@ public abstract class Lzcnt { internal Lzcnt() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } public abstract class X64 { internal X64() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// unsigned __int64 _lzcnt_u64 (unsigned __int64 a) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Pclmulqdq.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Pclmulqdq.PlatformNotSupported.cs index 7ad63c407f5c..b2b4898d38ec 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Pclmulqdq.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Pclmulqdq.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Pclmulqdq : Sse2 { internal Pclmulqdq() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m128i _mm_clmulepi64_si128 (__m128i a, __m128i b, const int imm8) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Popcnt.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Popcnt.PlatformNotSupported.cs index 0a46497d91f1..8fe62a1776eb 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Popcnt.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Popcnt.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; namespace System.Runtime.Intrinsics.X86 { @@ -15,12 +16,14 @@ public abstract class Popcnt : Sse42 { internal Popcnt() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } public new abstract class X64 : Sse41.X64 { internal X64() { } - public new static bool IsSupported { get { return false; } } + + public new static bool IsSupported { [Intrinsic] get { return false; } } + /// /// __int64 _mm_popcnt_u64 (unsigned __int64 a) /// POPCNT reg64, reg/m64 diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.PlatformNotSupported.cs index ce337e68775e..44d7b1fa7947 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Sse { internal Sse() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } public abstract class X64 { internal X64() { } - public static bool IsSupported { get { return false; } } + public static bool IsSupported { [Intrinsic] get { return false; } } /// /// __int64 _mm_cvtss_si64 (__m128 a) @@ -80,19 +81,19 @@ internal X64() { } /// int _mm_comieq_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomieq_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpeq_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(0) /// - public static Vector128 CompareEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpgt_ps (__m128 a, __m128 b) @@ -104,19 +105,19 @@ internal X64() { } /// int _mm_comigt_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomigt_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpgt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(6) /// - public static Vector128 CompareGreaterThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpge_ps (__m128 a, __m128 b) @@ -128,19 +129,19 @@ internal X64() { } /// int _mm_comige_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomige_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpge_ss (__m128 a, __m128 b) /// CMPPS xmm, xmm/m32, imm8(5) /// - public static Vector128 CompareGreaterThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmplt_ps (__m128 a, __m128 b) @@ -152,19 +153,19 @@ internal X64() { } /// int _mm_comilt_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomilt_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareLessThanUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmplt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(1) /// - public static Vector128 CompareLessThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmple_ps (__m128 a, __m128 b) @@ -176,19 +177,19 @@ internal X64() { } /// int _mm_comile_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomile_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmple_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(2) /// - public static Vector128 CompareLessThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpneq_ps (__m128 a, __m128 b) @@ -200,19 +201,19 @@ internal X64() { } /// int _mm_comineq_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareNotEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomineq_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareNotEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpneq_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(4) /// - public static Vector128 CompareNotEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpngt_ps (__m128 a, __m128 b) @@ -224,7 +225,7 @@ internal X64() { } /// __m128 _mm_cmpngt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(2) /// - public static Vector128 CompareNotGreaterThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpnge_ps (__m128 a, __m128 b) @@ -236,7 +237,7 @@ internal X64() { } /// __m128 _mm_cmpnge_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(1) /// - public static Vector128 CompareNotGreaterThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpnlt_ps (__m128 a, __m128 b) @@ -248,7 +249,7 @@ internal X64() { } /// __m128 _mm_cmpnlt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(5) /// - public static Vector128 CompareNotLessThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpnle_ps (__m128 a, __m128 b) @@ -260,7 +261,7 @@ internal X64() { } /// __m128 _mm_cmpnle_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(6) /// - public static Vector128 CompareNotLessThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpord_ps (__m128 a, __m128 b) @@ -272,7 +273,7 @@ internal X64() { } /// __m128 _mm_cmpord_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(7) /// - public static Vector128 CompareOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarOrdered(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128 _mm_cmpunord_ps (__m128 a, __m128 b) @@ -284,7 +285,7 @@ internal X64() { } /// __m128 _mm_cmpunord_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(3) /// - public static Vector128 CompareUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarUnordered(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_cvtss_si32 (__m128 a) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.cs index f65aedaf818a..c9c2d7e139e9 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse.cs @@ -83,19 +83,19 @@ internal X64() { } /// int _mm_comieq_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareEqualOrderedScalar(Vector128 left, Vector128 right) => CompareEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedEqual(Vector128 left, Vector128 right) => CompareScalarOrderedEqual(left, right); /// /// int _mm_ucomieq_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedEqual(left, right); /// /// __m128 _mm_cmpeq_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(0) /// - public static Vector128 CompareEqualScalar(Vector128 left, Vector128 right) => CompareEqualScalar(left, right); + public static Vector128 CompareScalarEqual(Vector128 left, Vector128 right) => CompareScalarEqual(left, right); /// /// __m128 _mm_cmpgt_ps (__m128 a, __m128 b) @@ -107,19 +107,19 @@ internal X64() { } /// int _mm_comigt_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrderedScalar(left, right); + public static bool CompareScalarOrderedGreaterThan(Vector128 left, Vector128 right) => CompareScalarOrderedGreaterThan(left, right); /// /// int _mm_ucomigt_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanUnorderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanUnorderedScalar(left, right); + public static bool CompareScalarUnorderedGreaterThan(Vector128 left, Vector128 right) => CompareScalarUnorderedGreaterThan(left, right); /// /// __m128 _mm_cmpgt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(6) /// - public static Vector128 CompareGreaterThanScalar(Vector128 left, Vector128 right) => CompareGreaterThanScalar(left, right); + public static Vector128 CompareScalarGreaterThan(Vector128 left, Vector128 right) => CompareScalarGreaterThan(left, right); /// /// __m128 _mm_cmpge_ps (__m128 a, __m128 b) @@ -131,19 +131,19 @@ internal X64() { } /// int _mm_comige_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrEqualOrderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarOrderedGreaterThanOrEqual(left, right); /// /// int _mm_ucomige_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareGreaterThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedGreaterThanOrEqual(left, right); /// /// __m128 _mm_cmpge_ss (__m128 a, __m128 b) /// CMPPS xmm, xmm/m32, imm8(5) /// - public static Vector128 CompareGreaterThanOrEqualScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualScalar(left, right); + public static Vector128 CompareScalarGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarGreaterThanOrEqual(left, right); /// /// __m128 _mm_cmplt_ps (__m128 a, __m128 b) @@ -155,19 +155,19 @@ internal X64() { } /// int _mm_comilt_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrderedScalar(left, right); + public static bool CompareScalarOrderedLessThan(Vector128 left, Vector128 right) => CompareScalarOrderedLessThan(left, right); /// /// int _mm_ucomilt_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareLessThanUnorderedScalar(Vector128 left, Vector128 right) => CompareLessThanUnorderedScalar(left, right); + public static bool CompareScalarUnorderedLessThan(Vector128 left, Vector128 right) => CompareScalarUnorderedLessThan(left, right); /// /// __m128 _mm_cmplt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(1) /// - public static Vector128 CompareLessThanScalar(Vector128 left, Vector128 right) => CompareLessThanScalar(left, right); + public static Vector128 CompareScalarLessThan(Vector128 left, Vector128 right) => CompareScalarLessThan(left, right); /// /// __m128 _mm_cmple_ps (__m128 a, __m128 b) @@ -179,19 +179,19 @@ internal X64() { } /// int _mm_comile_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrEqualOrderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarOrderedLessThanOrEqual(left, right); /// /// int _mm_ucomile_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareLessThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedLessThanOrEqual(left, right); /// /// __m128 _mm_cmple_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(2) /// - public static Vector128 CompareLessThanOrEqualScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualScalar(left, right); + public static Vector128 CompareScalarLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarLessThanOrEqual(left, right); /// /// __m128 _mm_cmpneq_ps (__m128 a, __m128 b) @@ -203,19 +203,19 @@ internal X64() { } /// int _mm_comineq_ss (__m128 a, __m128 b) /// COMISS xmm, xmm/m32 /// - public static bool CompareNotEqualOrderedScalar(Vector128 left, Vector128 right) => CompareNotEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedNotEqual(Vector128 left, Vector128 right) => CompareScalarOrderedNotEqual(left, right); /// /// int _mm_ucomineq_ss (__m128 a, __m128 b) /// UCOMISS xmm, xmm/m32 /// - public static bool CompareNotEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareNotEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedNotEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedNotEqual(left, right); /// /// __m128 _mm_cmpneq_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(4) /// - public static Vector128 CompareNotEqualScalar(Vector128 left, Vector128 right) => CompareNotEqualScalar(left, right); + public static Vector128 CompareScalarNotEqual(Vector128 left, Vector128 right) => CompareScalarNotEqual(left, right); /// /// __m128 _mm_cmpngt_ps (__m128 a, __m128 b) @@ -227,7 +227,7 @@ internal X64() { } /// __m128 _mm_cmpngt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(2) /// - public static Vector128 CompareNotGreaterThanScalar(Vector128 left, Vector128 right) => CompareNotGreaterThanScalar(left, right); + public static Vector128 CompareScalarNotGreaterThan(Vector128 left, Vector128 right) => CompareScalarNotGreaterThan(left, right); /// /// __m128 _mm_cmpnge_ps (__m128 a, __m128 b) @@ -239,7 +239,7 @@ internal X64() { } /// __m128 _mm_cmpnge_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(1) /// - public static Vector128 CompareNotGreaterThanOrEqualScalar(Vector128 left, Vector128 right) => CompareNotGreaterThanOrEqualScalar(left, right); + public static Vector128 CompareScalarNotGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarNotGreaterThanOrEqual(left, right); /// /// __m128 _mm_cmpnlt_ps (__m128 a, __m128 b) @@ -251,7 +251,7 @@ internal X64() { } /// __m128 _mm_cmpnlt_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(5) /// - public static Vector128 CompareNotLessThanScalar(Vector128 left, Vector128 right) => CompareNotLessThanScalar(left, right); + public static Vector128 CompareScalarNotLessThan(Vector128 left, Vector128 right) => CompareScalarNotLessThan(left, right); /// /// __m128 _mm_cmpnle_ps (__m128 a, __m128 b) @@ -263,7 +263,7 @@ internal X64() { } /// __m128 _mm_cmpnle_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(6) /// - public static Vector128 CompareNotLessThanOrEqualScalar(Vector128 left, Vector128 right) => CompareNotLessThanOrEqualScalar(left, right); + public static Vector128 CompareScalarNotLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarNotLessThanOrEqual(left, right); /// /// __m128 _mm_cmpord_ps (__m128 a, __m128 b) @@ -275,7 +275,7 @@ internal X64() { } /// __m128 _mm_cmpord_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(7) /// - public static Vector128 CompareOrderedScalar(Vector128 left, Vector128 right) => CompareOrderedScalar(left, right); + public static Vector128 CompareScalarOrdered(Vector128 left, Vector128 right) => CompareScalarOrdered(left, right); /// /// __m128 _mm_cmpunord_ps (__m128 a, __m128 b) @@ -287,7 +287,7 @@ internal X64() { } /// __m128 _mm_cmpunord_ss (__m128 a, __m128 b) /// CMPSS xmm, xmm/m32, imm8(3) /// - public static Vector128 CompareUnorderedScalar(Vector128 left, Vector128 right) => CompareUnorderedScalar(left, right); + public static Vector128 CompareScalarUnordered(Vector128 left, Vector128 right) => CompareScalarUnordered(left, right); /// /// int _mm_cvtss_si32 (__m128 a) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs index 3085aba002e4..baec7403109f 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Sse2 : Sse { internal Sse2() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } public new abstract class X64 : Sse.X64 { internal X64() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __int64 _mm_cvtsd_si64 (__m128d a) @@ -300,21 +301,21 @@ internal X64() { } /// /// int _mm_comieq_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomieq_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpeq_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(0) /// - public static Vector128 CompareEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cmpgt_epi8 (__m128i a, __m128i b) @@ -339,21 +340,21 @@ internal X64() { } /// /// int _mm_comigt_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomigt_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpgt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(6) /// - public static Vector128 CompareGreaterThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpge_pd (__m128d a, __m128d b) @@ -363,21 +364,21 @@ internal X64() { } /// /// int _mm_comige_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomige_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpge_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(5) /// - public static Vector128 CompareGreaterThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cmplt_epi8 (__m128i a, __m128i b) @@ -402,21 +403,21 @@ internal X64() { } /// /// int _mm_comilt_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomilt_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareLessThanUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmplt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(1) /// - public static Vector128 CompareLessThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmple_pd (__m128d a, __m128d b) @@ -426,21 +427,21 @@ internal X64() { } /// /// int _mm_comile_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomile_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmple_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(2) /// - public static Vector128 CompareLessThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpneq_pd (__m128d a, __m128d b) @@ -450,21 +451,21 @@ internal X64() { } /// /// int _mm_comineq_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareNotEqualOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarOrderedNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// int _mm_ucomineq_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareNotEqualUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static bool CompareScalarUnorderedNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpneq_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(4) /// - public static Vector128 CompareNotEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpngt_pd (__m128d a, __m128d b) @@ -476,7 +477,7 @@ internal X64() { } /// __m128d _mm_cmpngt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(2) /// - public static Vector128 CompareNotGreaterThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotGreaterThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpnge_pd (__m128d a, __m128d b) @@ -488,7 +489,7 @@ internal X64() { } /// __m128d _mm_cmpnge_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(1) /// - public static Vector128 CompareNotGreaterThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotGreaterThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpnlt_pd (__m128d a, __m128d b) @@ -500,7 +501,7 @@ internal X64() { } /// __m128d _mm_cmpnlt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(5) /// - public static Vector128 CompareNotLessThanScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotLessThan(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpnle_pd (__m128d a, __m128d b) @@ -512,7 +513,7 @@ internal X64() { } /// __m128d _mm_cmpnle_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(6) /// - public static Vector128 CompareNotLessThanOrEqualScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarNotLessThanOrEqual(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpord_pd (__m128d a, __m128d b) @@ -524,7 +525,7 @@ internal X64() { } /// __m128d _mm_cmpord_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(7) /// - public static Vector128 CompareOrderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarOrdered(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128d _mm_cmpunord_pd (__m128d a, __m128d b) @@ -536,7 +537,7 @@ internal X64() { } /// __m128d _mm_cmpunord_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(3) /// - public static Vector128 CompareUnorderedScalar(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } + public static Vector128 CompareScalarUnordered(Vector128 left, Vector128 right) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtps_epi32 (__m128 a) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.cs index d9d1307cc869..968240ee3745 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse2.cs @@ -303,21 +303,21 @@ internal X64() { } /// /// int _mm_comieq_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareEqualOrderedScalar(Vector128 left, Vector128 right) => CompareEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedEqual(Vector128 left, Vector128 right) => CompareScalarOrderedEqual(left, right); /// /// int _mm_ucomieq_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedEqual(left, right); /// /// __m128d _mm_cmpeq_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(0) /// - public static Vector128 CompareEqualScalar(Vector128 left, Vector128 right) => CompareEqualScalar(left, right); + public static Vector128 CompareScalarEqual(Vector128 left, Vector128 right) => CompareScalarEqual(left, right); /// /// __m128i _mm_cmpgt_epi8 (__m128i a, __m128i b) @@ -342,21 +342,21 @@ internal X64() { } /// /// int _mm_comigt_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrderedScalar(left, right); + public static bool CompareScalarOrderedGreaterThan(Vector128 left, Vector128 right) => CompareScalarOrderedGreaterThan(left, right); /// /// int _mm_ucomigt_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanUnorderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanUnorderedScalar(left, right); + public static bool CompareScalarUnorderedGreaterThan(Vector128 left, Vector128 right) => CompareScalarUnorderedGreaterThan(left, right); /// /// __m128d _mm_cmpgt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(6) /// - public static Vector128 CompareGreaterThanScalar(Vector128 left, Vector128 right) => CompareGreaterThanScalar(left, right); + public static Vector128 CompareScalarGreaterThan(Vector128 left, Vector128 right) => CompareScalarGreaterThan(left, right); /// /// __m128d _mm_cmpge_pd (__m128d a, __m128d b) @@ -366,21 +366,21 @@ internal X64() { } /// /// int _mm_comige_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrEqualOrderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarOrderedGreaterThanOrEqual(left, right); /// /// int _mm_ucomige_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareGreaterThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedGreaterThanOrEqual(left, right); /// /// __m128d _mm_cmpge_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(5) /// - public static Vector128 CompareGreaterThanOrEqualScalar(Vector128 left, Vector128 right) => CompareGreaterThanOrEqualScalar(left, right); + public static Vector128 CompareScalarGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarGreaterThanOrEqual(left, right); /// /// __m128i _mm_cmplt_epi8 (__m128i a, __m128i b) @@ -405,21 +405,21 @@ internal X64() { } /// /// int _mm_comilt_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrderedScalar(left, right); + public static bool CompareScalarOrderedLessThan(Vector128 left, Vector128 right) => CompareScalarOrderedLessThan(left, right); /// /// int _mm_ucomilt_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareLessThanUnorderedScalar(Vector128 left, Vector128 right) => CompareLessThanUnorderedScalar(left, right); + public static bool CompareScalarUnorderedLessThan(Vector128 left, Vector128 right) => CompareScalarUnorderedLessThan(left, right); /// /// __m128d _mm_cmplt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(1) /// - public static Vector128 CompareLessThanScalar(Vector128 left, Vector128 right) => CompareLessThanScalar(left, right); + public static Vector128 CompareScalarLessThan(Vector128 left, Vector128 right) => CompareScalarLessThan(left, right); /// /// __m128d _mm_cmple_pd (__m128d a, __m128d b) @@ -429,21 +429,21 @@ internal X64() { } /// /// int _mm_comile_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrEqualOrderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarOrderedLessThanOrEqual(left, right); /// /// int _mm_ucomile_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareLessThanOrEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedLessThanOrEqual(left, right); /// /// __m128d _mm_cmple_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(2) /// - public static Vector128 CompareLessThanOrEqualScalar(Vector128 left, Vector128 right) => CompareLessThanOrEqualScalar(left, right); + public static Vector128 CompareScalarLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarLessThanOrEqual(left, right); /// /// __m128d _mm_cmpneq_pd (__m128d a, __m128d b) @@ -453,21 +453,21 @@ internal X64() { } /// /// int _mm_comineq_sd (__m128d a, __m128d b) - /// COMISS xmm, xmm/m64 + /// COMISD xmm, xmm/m64 /// - public static bool CompareNotEqualOrderedScalar(Vector128 left, Vector128 right) => CompareNotEqualOrderedScalar(left, right); + public static bool CompareScalarOrderedNotEqual(Vector128 left, Vector128 right) => CompareScalarOrderedNotEqual(left, right); /// /// int _mm_ucomineq_sd (__m128d a, __m128d b) - /// UCOMISS xmm, xmm/m64 + /// UCOMISD xmm, xmm/m64 /// - public static bool CompareNotEqualUnorderedScalar(Vector128 left, Vector128 right) => CompareNotEqualUnorderedScalar(left, right); + public static bool CompareScalarUnorderedNotEqual(Vector128 left, Vector128 right) => CompareScalarUnorderedNotEqual(left, right); /// /// __m128d _mm_cmpneq_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(4) /// - public static Vector128 CompareNotEqualScalar(Vector128 left, Vector128 right) => CompareNotEqualScalar(left, right); + public static Vector128 CompareScalarNotEqual(Vector128 left, Vector128 right) => CompareScalarNotEqual(left, right); /// /// __m128d _mm_cmpngt_pd (__m128d a, __m128d b) @@ -479,7 +479,7 @@ internal X64() { } /// __m128d _mm_cmpngt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(2) /// - public static Vector128 CompareNotGreaterThanScalar(Vector128 left, Vector128 right) => CompareNotGreaterThanScalar(left, right); + public static Vector128 CompareScalarNotGreaterThan(Vector128 left, Vector128 right) => CompareScalarNotGreaterThan(left, right); /// /// __m128d _mm_cmpnge_pd (__m128d a, __m128d b) @@ -491,7 +491,7 @@ internal X64() { } /// __m128d _mm_cmpnge_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(1) /// - public static Vector128 CompareNotGreaterThanOrEqualScalar(Vector128 left, Vector128 right) => CompareNotGreaterThanOrEqualScalar(left, right); + public static Vector128 CompareScalarNotGreaterThanOrEqual(Vector128 left, Vector128 right) => CompareScalarNotGreaterThanOrEqual(left, right); /// /// __m128d _mm_cmpnlt_pd (__m128d a, __m128d b) @@ -503,7 +503,7 @@ internal X64() { } /// __m128d _mm_cmpnlt_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(5) /// - public static Vector128 CompareNotLessThanScalar(Vector128 left, Vector128 right) => CompareNotLessThanScalar(left, right); + public static Vector128 CompareScalarNotLessThan(Vector128 left, Vector128 right) => CompareScalarNotLessThan(left, right); /// /// __m128d _mm_cmpnle_pd (__m128d a, __m128d b) @@ -515,7 +515,7 @@ internal X64() { } /// __m128d _mm_cmpnle_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(6) /// - public static Vector128 CompareNotLessThanOrEqualScalar(Vector128 left, Vector128 right) => CompareNotLessThanOrEqualScalar(left, right); + public static Vector128 CompareScalarNotLessThanOrEqual(Vector128 left, Vector128 right) => CompareScalarNotLessThanOrEqual(left, right); /// /// __m128d _mm_cmpord_pd (__m128d a, __m128d b) @@ -527,7 +527,7 @@ internal X64() { } /// __m128d _mm_cmpord_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(7) /// - public static Vector128 CompareOrderedScalar(Vector128 left, Vector128 right) => CompareOrderedScalar(left, right); + public static Vector128 CompareScalarOrdered(Vector128 left, Vector128 right) => CompareScalarOrdered(left, right); /// /// __m128d _mm_cmpunord_pd (__m128d a, __m128d b) @@ -539,7 +539,7 @@ internal X64() { } /// __m128d _mm_cmpunord_sd (__m128d a, __m128d b) /// CMPSD xmm, xmm/m64, imm8(3) /// - public static Vector128 CompareUnorderedScalar(Vector128 left, Vector128 right) => CompareUnorderedScalar(left, right); + public static Vector128 CompareScalarUnordered(Vector128 left, Vector128 right) => CompareScalarUnordered(left, right); /// /// __m128i _mm_cvtps_epi32 (__m128 a) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse3.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse3.PlatformNotSupported.cs index d97d4fd544d4..20bc6ae4cf19 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse3.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse3.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Sse3 : Sse2 { internal Sse3() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m128 _mm_addsub_ps (__m128 a, __m128 b) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.PlatformNotSupported.cs index 25259a957879..d9f67bcb6791 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Sse41 : Ssse3 { internal Sse41() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } public new abstract class X64 : Sse2.X64 { internal X64() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __int64 _mm_extract_epi64 (__m128i a, const int imm8) @@ -180,65 +181,126 @@ internal X64() { } /// /// __m128i _mm_cvtepi8_epi16 (__m128i a) - /// PMOVSXBW xmm, xmm/m64 + /// PMOVSXBW xmm, xmm /// public static Vector128 ConvertToVector128Int16(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu8_epi16 (__m128i a) - /// PMOVZXBW xmm, xmm/m64 + /// PMOVZXBW xmm, xmm /// public static Vector128 ConvertToVector128Int16(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepi8_epi32 (__m128i a) - /// PMOVSXBD xmm, xmm/m32 + /// PMOVSXBD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu8_epi32 (__m128i a) - /// PMOVZXBD xmm, xmm/m32 + /// PMOVZXBD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepi16_epi32 (__m128i a) - /// PMOVSXWD xmm, xmm/m64 + /// PMOVSXWD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu16_epi32 (__m128i a) - /// PMOVZXWD xmm, xmm/m64 + /// PMOVZXWD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepi8_epi64 (__m128i a) - /// PMOVSXBQ xmm, xmm/m16 + /// PMOVSXBQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu8_epi64 (__m128i a) - /// PMOVZXBQ xmm, xmm/m16 + /// PMOVZXBQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepi16_epi64 (__m128i a) - /// PMOVSXWQ xmm, xmm/m32 + /// PMOVSXWQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu16_epi64 (__m128i a) - /// PMOVZXWQ xmm, xmm/m32 + /// PMOVZXWQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepi32_epi64 (__m128i a) - /// PMOVSXDQ xmm, xmm/m64 + /// PMOVSXDQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } /// /// __m128i _mm_cvtepu32_epi64 (__m128i a) - /// PMOVZXDQ xmm, xmm/m64 + /// PMOVZXDQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXBW xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int16(sbyte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXBW xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int16(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXBD xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(sbyte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXBD xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXWD xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(short* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXWD xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(ushort* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXBQ xmm, m16 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(sbyte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXBQ xmm, m16 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(byte* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXWQ xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(short* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXWQ xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(ushort* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVSXDQ xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(int* address) { throw new PlatformNotSupportedException(); } + /// + /// PMOVZXDQ xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(uint* address) { throw new PlatformNotSupportedException(); } + /// /// __m128 _mm_dp_ps (__m128 a, __m128 b, const int imm8) /// DPPS xmm, xmm/m128, imm8 diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.cs index 834b48856f9c..8fe0a3cf944f 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse41.cs @@ -183,65 +183,126 @@ internal X64() { } /// /// __m128i _mm_cvtepi8_epi16 (__m128i a) - /// PMOVSXBW xmm, xmm/m64 + /// PMOVSXBW xmm, xmm /// public static Vector128 ConvertToVector128Int16(Vector128 value) => ConvertToVector128Int16(value); /// /// __m128i _mm_cvtepu8_epi16 (__m128i a) - /// PMOVZXBW xmm, xmm/m64 + /// PMOVZXBW xmm, xmm /// public static Vector128 ConvertToVector128Int16(Vector128 value) => ConvertToVector128Int16(value); /// /// __m128i _mm_cvtepi8_epi32 (__m128i a) - /// PMOVSXBD xmm, xmm/m32 + /// PMOVSXBD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) => ConvertToVector128Int32(value); /// /// __m128i _mm_cvtepu8_epi32 (__m128i a) - /// PMOVZXBD xmm, xmm/m32 + /// PMOVZXBD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) => ConvertToVector128Int32(value); /// /// __m128i _mm_cvtepi16_epi32 (__m128i a) - /// PMOVSXWD xmm, xmm/m64 + /// PMOVSXWD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) => ConvertToVector128Int32(value); /// /// __m128i _mm_cvtepu16_epi32 (__m128i a) - /// PMOVZXWD xmm, xmm/m64 + /// PMOVZXWD xmm, xmm /// public static Vector128 ConvertToVector128Int32(Vector128 value) => ConvertToVector128Int32(value); /// /// __m128i _mm_cvtepi8_epi64 (__m128i a) - /// PMOVSXBQ xmm, xmm/m16 + /// PMOVSXBQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); /// /// __m128i _mm_cvtepu8_epi64 (__m128i a) - /// PMOVZXBQ xmm, xmm/m16 + /// PMOVZXBQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); /// /// __m128i _mm_cvtepi16_epi64 (__m128i a) - /// PMOVSXWQ xmm, xmm/m32 + /// PMOVSXWQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); /// /// __m128i _mm_cvtepu16_epi64 (__m128i a) - /// PMOVZXWQ xmm, xmm/m32 + /// PMOVZXWQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); /// /// __m128i _mm_cvtepi32_epi64 (__m128i a) - /// PMOVSXDQ xmm, xmm/m64 + /// PMOVSXDQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); /// /// __m128i _mm_cvtepu32_epi64 (__m128i a) - /// PMOVZXDQ xmm, xmm/m64 + /// PMOVZXDQ xmm, xmm /// public static Vector128 ConvertToVector128Int64(Vector128 value) => ConvertToVector128Int64(value); + /// + /// PMOVSXBW xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int16(sbyte* address) => ConvertToVector128Int16(address); + /// + /// PMOVZXBW xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int16(byte* address) => ConvertToVector128Int16(address); + /// + /// PMOVSXBD xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(sbyte* address) => ConvertToVector128Int32(address); + /// + /// PMOVZXBD xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(byte* address) => ConvertToVector128Int32(address); + /// + /// PMOVSXWD xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(short* address) => ConvertToVector128Int32(address); + /// + /// PMOVZXWD xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int32(ushort* address) => ConvertToVector128Int32(address); + /// + /// PMOVSXBQ xmm, m16 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(sbyte* address) => ConvertToVector128Int64(address); + /// + /// PMOVZXBQ xmm, m16 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(byte* address) => ConvertToVector128Int64(address); + /// + /// PMOVSXWQ xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(short* address) => ConvertToVector128Int64(address); + /// + /// PMOVZXWQ xmm, m32 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(ushort* address) => ConvertToVector128Int64(address); + /// + /// PMOVSXDQ xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(int* address) => ConvertToVector128Int64(address); + /// + /// PMOVZXDQ xmm, m64 + /// The native signature does not exist. We provide this additional overload for completeness. + /// + public static unsafe Vector128 ConvertToVector128Int64(uint* address) => ConvertToVector128Int64(address); + /// /// __m128 _mm_dp_ps (__m128 a, __m128 b, const int imm8) /// DPPS xmm, xmm/m128, imm8 diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse42.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse42.PlatformNotSupported.cs index ace4a3f0ecf5..ac4b45af018d 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse42.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Sse42.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,13 +17,13 @@ public abstract class Sse42 : Sse41 { internal Sse42() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } public new abstract class X64 : Sse41.X64 { internal X64() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// unsigned __int64 _mm_crc32_u64 (unsigned __int64 crc, unsigned __int64 v) diff --git a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Ssse3.PlatformNotSupported.cs b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Ssse3.PlatformNotSupported.cs index 8aa0790e0333..cd66c76161af 100644 --- a/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Ssse3.PlatformNotSupported.cs +++ b/src/Common/src/CoreLib/System/Runtime/Intrinsics/X86/Ssse3.PlatformNotSupported.cs @@ -4,6 +4,7 @@ #nullable enable using System; +using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; namespace System.Runtime.Intrinsics.X86 @@ -16,7 +17,7 @@ public abstract class Ssse3 : Sse3 { internal Ssse3() { } - public new static bool IsSupported { get { return false; } } + public new static bool IsSupported { [Intrinsic] get { return false; } } /// /// __m128i _mm_abs_epi8 (__m128i a) diff --git a/src/Common/src/CoreLib/System/Runtime/Loader/AssemblyLoadContext.cs b/src/Common/src/CoreLib/System/Runtime/Loader/AssemblyLoadContext.cs index a2c8df0ba8a2..70d5f10cf3d2 100644 --- a/src/Common/src/CoreLib/System/Runtime/Loader/AssemblyLoadContext.cs +++ b/src/Common/src/CoreLib/System/Runtime/Loader/AssemblyLoadContext.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Reflection; @@ -31,18 +32,33 @@ private enum InternalState private static readonly Dictionary> s_allContexts = new Dictionary>(); private static long s_nextId; - // Indicates the state of this ALC (Alive or in Unloading state) - private InternalState _state; - - // Id used by s_allContexts - private readonly long _id; +#region private data members + // If you modify any of these fields, you must also update the + // AssemblyLoadContextBaseObject structure in object.h // synchronization primitive to protect against usage of this instance while unloading private readonly object _unloadLock; + private event Func _resolvingUnmanagedDll; + + private event Func _resolving; + + private event Action _unloading; + + private readonly string _name; + // Contains the reference to VM's representation of the AssemblyLoadContext private readonly IntPtr _nativeAssemblyLoadContext; + // Id used by s_allContexts + private readonly long _id; + + // Indicates the state of this ALC (Alive or in Unloading state) + private InternalState _state; + + private readonly bool _isCollectible; +#endregion + protected AssemblyLoadContext() : this(false, false, null) { } @@ -58,9 +74,9 @@ public AssemblyLoadContext(string name, bool isCollectible = false) : this(false private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible, string name) { // Initialize the VM side of AssemblyLoadContext if not already done. - IsCollectible = isCollectible; + _isCollectible = isCollectible; - Name = name; + _name = name; // The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer // even in case the following allocation fails or the thread is aborted between these two lines. @@ -103,7 +119,7 @@ private protected AssemblyLoadContext(bool representsTPALoadContext, bool isColl private void RaiseUnloadEvent() { // Ensure that we raise the Unload event only once - Interlocked.Exchange(ref Unloading, null)?.Invoke(this); + Interlocked.Exchange(ref _unloading, null)?.Invoke(this); } private void InitiateUnload() @@ -150,20 +166,50 @@ public IEnumerable Assemblies // Event handler for resolving native libraries. // This event is raised if the native library could not be resolved via // the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()] - // + // // Inputs: Invoking assembly, and library name to resolve // Returns: A handle to the loaded native library - public event Func ResolvingUnmanagedDll; + public event Func ResolvingUnmanagedDll + { + add + { + _resolvingUnmanagedDll += value; + } + remove + { + _resolvingUnmanagedDll -= value; + } + } // Event handler for resolving managed assemblies. // This event is raised if the managed assembly could not be resolved via // the default resolution logic [including AssemblyLoadContext.Load()] - // + // // Inputs: The AssemblyLoadContext and AssemblyName to be loaded // Returns: The Loaded assembly object. - public event Func Resolving; + public event Func Resolving + { + add + { + _resolving += value; + } + remove + { + _resolving -= value; + } + } - public event Action Unloading; + public event Action Unloading + { + add + { + _unloading += value; + } + remove + { + _unloading -= value; + } + } // Occurs when an Assembly is loaded public static event AssemblyLoadEventHandler AssemblyLoad; @@ -180,9 +226,9 @@ public IEnumerable Assemblies public static AssemblyLoadContext Default => DefaultAssemblyLoadContext.s_loadContext; - public bool IsCollectible { get; } + public bool IsCollectible { get { return _isCollectible;} } - public string Name { get; } + public string Name { get { return _name;} } public override string ToString() => "\"" + Name + "\" " + GetType().ToString() + " #" + _id; @@ -240,10 +286,10 @@ public Assembly LoadFromAssemblyName(AssemblyName assemblyName) // Attempt to load the assembly, using the same ordering as static load, in the current load context. StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; - return Assembly.Load(assemblyName, ref stackMark, _nativeAssemblyLoadContext); + return Assembly.Load(assemblyName, ref stackMark, this); } - // These methods load assemblies into the current AssemblyLoadContext + // These methods load assemblies into the current AssemblyLoadContext // They may be used in the implementation of an AssemblyLoadContext derivation public Assembly LoadFromAssemblyPath(string assemblyPath) { @@ -288,7 +334,7 @@ public Assembly LoadFromNativeImagePath(string nativeImagePath, string assemblyP return InternalLoadFromPath(assemblyPath, nativeImagePath); } - } + } public Assembly LoadFromStream(Stream assembly) { @@ -362,7 +408,7 @@ protected virtual IntPtr LoadUnmanagedDll(string unmanagedDllName) { //defer to default coreclr policy of loading unmanaged dll return IntPtr.Zero; - } + } public void Unload() { @@ -387,7 +433,7 @@ internal static void OnProcessExit() } } } - } + } private void VerifyIsAlive() { @@ -396,6 +442,119 @@ private void VerifyIsAlive() throw new InvalidOperationException(SR.AssemblyLoadContext_Verify_NotUnloading); } } + + private static AsyncLocal s_asyncLocalCurrent; + + /// Nullable current AssemblyLoadContext used for context sensitive reflection APIs + /// + /// This is an advanced setting used in reflection assembly loading scenarios. + /// + /// There are a set of contextual reflection APIs which load managed assemblies through an inferred AssemblyLoadContext. + /// * + /// * + /// * + /// * + /// + /// When CurrentContextualReflectionContext is null, the AssemblyLoadContext is inferred. + /// The inference logic is simple. + /// * For static methods, it is the AssemblyLoadContext which loaded the method caller's assembly. + /// * For instance methods, it is the AssemblyLoadContext which loaded the instance's assembly. + /// + /// When this property is set, the CurrentContextualReflectionContext value is used by these contextual reflection APIs for loading. + /// + /// This property is typically set in a using block by + /// . + /// + /// The property is stored in an AsyncLocal<AssemblyLoadContext>. This means the setting can be unique for every async or thread in the process. + /// + /// For more details see https://github.com/dotnet/coreclr/blob/master/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md + /// + public static AssemblyLoadContext CurrentContextualReflectionContext + { + get { return s_asyncLocalCurrent?.Value; } + } + + private static void SetCurrentContextualReflectionContext(AssemblyLoadContext value) + { + if (s_asyncLocalCurrent == null) + { + Interlocked.CompareExchange(ref s_asyncLocalCurrent, new AsyncLocal(), null); + } + s_asyncLocalCurrent.Value = value; + } + + /// Enter scope using this AssemblyLoadContext for ContextualReflection + /// A disposable ContextualReflectionScope for use in a using block + /// + /// Sets CurrentContextualReflectionContext to this instance. + /// + /// + /// Returns a disposable ContextualReflectionScope for use in a using block. When the using calls the + /// Dispose() method, it restores the ContextualReflectionScope to its previous value. + /// + public ContextualReflectionScope EnterContextualReflection() + { + return new ContextualReflectionScope(this); + } + + /// Enter scope using this AssemblyLoadContext for ContextualReflection + /// Set CurrentContextualReflectionContext to the AssemblyLoadContext which loaded activating. + /// A disposable ContextualReflectionScope for use in a using block + /// + /// Sets CurrentContextualReflectionContext to to the AssemblyLoadContext which loaded activating. + /// + /// + /// Returns a disposable ContextualReflectionScope for use in a using block. When the using calls the + /// Dispose() method, it restores the ContextualReflectionScope to its previous value. + /// + public static ContextualReflectionScope EnterContextualReflection(Assembly activating) + { + if (activating == null) + return new ContextualReflectionScope(null); + + AssemblyLoadContext assemblyLoadContext = GetLoadContext(activating); + + if (assemblyLoadContext == null) + { + // All RuntimeAssemblies & Only RuntimeAssemblies have an AssemblyLoadContext + throw new ArgumentException(SR.Arg_MustBeRuntimeAssembly, nameof(activating)); + } + + return assemblyLoadContext.EnterContextualReflection(); + } + + /// Opaque disposable struct used to restore CurrentContextualReflectionContext + /// + /// This is an implmentation detail of the AssemblyLoadContext.EnterContextualReflection APIs. + /// It is a struct, to avoid heap allocation. + /// It is required to be public to avoid boxing. + /// + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public struct ContextualReflectionScope : IDisposable + { + private readonly AssemblyLoadContext _activated; + private readonly AssemblyLoadContext _predecessor; + private readonly bool _initialized; + + internal ContextualReflectionScope(AssemblyLoadContext activating) + { + _predecessor = AssemblyLoadContext.CurrentContextualReflectionContext; + AssemblyLoadContext.SetCurrentContextualReflectionContext(activating); + _activated = activating; + _initialized = true; + } + + public void Dispose() + { + if (_initialized) + { + // Do not clear initialized. Always restore the _predecessor in Dispose() + // _initialized = false; + AssemblyLoadContext.SetCurrentContextualReflectionContext(_predecessor); + } + } + } } internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext diff --git a/src/Common/src/CoreLib/System/Span.Fast.cs b/src/Common/src/CoreLib/System/Span.Fast.cs index adc1f3903d14..c884989c6da7 100644 --- a/src/Common/src/CoreLib/System/Span.Fast.cs +++ b/src/Common/src/CoreLib/System/Span.Fast.cs @@ -159,18 +159,6 @@ public ref T this[int index] #endif } - public ref T this[Index index] - { - get - { - // Evaluate the actual index first because it helps performance - int actualIndex = index.GetOffset(_length); - return ref this [actualIndex]; - } - } - - public Span this[Range range] => Slice(range); - /// /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference. /// It can be used for pinning and is required to support the use of span within a fixed statement. @@ -376,28 +364,6 @@ public Span Slice(int start, int length) return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); } - /// - /// Forms a slice out of the given span, beginning at 'startIndex' - /// - /// The index at which to begin this slice. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(Index startIndex) - { - int actualIndex = startIndex.GetOffset(_length); - return Slice(actualIndex); - } - - /// - /// Forms a slice out of the given span, beginning at range start index to the range end - /// - /// The range which has the start and end indexes used to slice the span. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span Slice(Range range) - { - (int start, int length) = range.GetOffsetAndLength(_length); - return new Span(ref Unsafe.Add(ref _pointer.Value, start), length); - } - /// /// Copies the contents of this span into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes diff --git a/src/Common/src/CoreLib/System/String.Manipulation.cs b/src/Common/src/CoreLib/System/String.Manipulation.cs index dc7f01591bb0..278a3b6b98a5 100644 --- a/src/Common/src/CoreLib/System/String.Manipulation.cs +++ b/src/Common/src/CoreLib/System/String.Manipulation.cs @@ -1680,20 +1680,6 @@ public string Substring(int startIndex, int length) return InternalSubString(startIndex, length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string Substring(Index startIndex) - { - int actualIndex = startIndex.GetOffset(Length); - return Substring(actualIndex); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string Substring(Range range) - { - (int start, int length) = range.GetOffsetAndLength(Length); - return Substring(start, length); - } - private unsafe string InternalSubString(int startIndex, int length) { Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!"); diff --git a/src/Common/src/CoreLib/System/String.cs b/src/Common/src/CoreLib/System/String.cs index 3ae88bbe8119..3701434defa1 100644 --- a/src/Common/src/CoreLib/System/String.cs +++ b/src/Common/src/CoreLib/System/String.cs @@ -448,19 +448,6 @@ public static bool IsNullOrEmpty(string? value) return (value == null || 0u >= (uint)value.Length) ? true : false; } - [System.Runtime.CompilerServices.IndexerName("Chars")] - public char this[Index index] - { - get - { - int actualIndex = index.GetOffset(Length); - return this[actualIndex]; - } - } - - [System.Runtime.CompilerServices.IndexerName("Chars")] - public string this[Range range] => Substring(range); - public static bool IsNullOrWhiteSpace(string? value) { if (value == null) return true; diff --git a/src/Common/src/CoreLib/System/Text/ASCIIEncoding.cs b/src/Common/src/CoreLib/System/Text/ASCIIEncoding.cs index 8cf1f57ccb10..f0fcc3b3a667 100644 --- a/src/Common/src/CoreLib/System/Text/ASCIIEncoding.cs +++ b/src/Common/src/CoreLib/System/Text/ASCIIEncoding.cs @@ -159,7 +159,7 @@ private unsafe int GetByteCountCommon(char* pChars, int charCount) // Common helper method for all non-EncoderNLS entry points to GetByteCount. // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - Debug.Assert(charCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); // First call into the fast path. @@ -340,9 +340,9 @@ private unsafe int GetBytesCommon(char* pChars, int charCount, byte* pBytes, int // Common helper method for all non-EncoderNLS entry points to GetBytes. // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - Debug.Assert(charCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); - Debug.Assert(byteCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); // First call into the fast path. @@ -496,7 +496,7 @@ private unsafe int GetCharCountCommon(byte* pBytes, int byteCount) // Common helper method for all non-DecoderNLS entry points to GetCharCount. // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - Debug.Assert(byteCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); // First call into the fast path. @@ -624,9 +624,9 @@ private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int // Common helper method for all non-DecoderNLS entry points to GetChars. // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - Debug.Assert(byteCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); - Debug.Assert(charCount >= 0, "Caller should't specify negative length buffer."); + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); // First call into the fast path. diff --git a/src/Common/src/CoreLib/System/Text/ASCIIUtility.Helpers.cs b/src/Common/src/CoreLib/System/Text/ASCIIUtility.Helpers.cs new file mode 100644 index 000000000000..731d52ab822c --- /dev/null +++ b/src/Common/src/CoreLib/System/Text/ASCIIUtility.Helpers.cs @@ -0,0 +1,108 @@ +// 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.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; + +namespace System.Text +{ + internal static partial class ASCIIUtility + { + /// + /// A mask which selects only the high bit of each byte of the given . + /// + private const uint UInt32HighBitsOnlyMask = 0x80808080u; + + /// + /// A mask which selects only the high bit of each byte of the given . + /// + private const ulong UInt64HighBitsOnlyMask = 0x80808080_80808080ul; + + /// + /// Returns iff all bytes in are ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool AllBytesInUInt32AreAscii(uint value) + { + // If the high bit of any byte is set, that byte is non-ASCII. + + return (value & UInt32HighBitsOnlyMask) == 0; + } + + /// + /// Given a DWORD which represents a four-byte buffer read in machine endianness, and which + /// the caller has asserted contains a non-ASCII byte *somewhere* in the data, counts the + /// number of consecutive ASCII bytes starting from the beginning of the buffer. Returns + /// a value 0 - 3, inclusive. (The caller is responsible for ensuring that the buffer doesn't + /// contain all-ASCII data.) + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint CountNumberOfLeadingAsciiBytesFromUInt32WithSomeNonAsciiData(uint value) + { + Debug.Assert(!AllBytesInUInt32AreAscii(value), "Caller shouldn't provide an all-ASCII value."); + + // Use BMI1 directly rather than going through BitOperations. We only see a perf gain here + // if we're able to emit a real tzcnt instruction; the software fallback used by BitOperations + // is too slow for our purposes since we can provide our own faster, specialized software fallback. + + if (Bmi1.IsSupported) + { + Debug.Assert(BitConverter.IsLittleEndian); + return Bmi1.TrailingZeroCount(value & UInt32HighBitsOnlyMask) >> 3; + } + + // Couldn't emit tzcnt, use specialized software fallback. + // The 'allBytesUpToNowAreAscii' DWORD uses bit twiddling to hold a 1 or a 0 depending + // on whether all processed bytes were ASCII. Then we accumulate all of the + // results to calculate how many consecutive ASCII bytes are present. + + value = ~value; + + if (BitConverter.IsLittleEndian) + { + // Read first byte + value >>= 7; + uint allBytesUpToNowAreAscii = value & 1; + uint numAsciiBytes = allBytesUpToNowAreAscii; + + // Read second byte + value >>= 8; + allBytesUpToNowAreAscii &= value; + numAsciiBytes += allBytesUpToNowAreAscii; + + // Read third byte + value >>= 8; + allBytesUpToNowAreAscii &= value; + numAsciiBytes += allBytesUpToNowAreAscii; + + return numAsciiBytes; + } + else + { + // BinaryPrimitives.ReverseEndianness is only implemented as an intrinsic on + // little-endian platforms, so using it in this big-endian path would be too + // expensive. Instead we'll just change how we perform the shifts. + + // Read first byte + value = BitOperations.RotateLeft(value, 1); + uint allBytesUpToNowAreAscii = value & 1; + uint numAsciiBytes = allBytesUpToNowAreAscii; + + // Read second byte + value = BitOperations.RotateLeft(value, 8); + allBytesUpToNowAreAscii &= value; + numAsciiBytes += allBytesUpToNowAreAscii; + + // Read third byte + value = BitOperations.RotateLeft(value, 8); + allBytesUpToNowAreAscii &= value; + numAsciiBytes += allBytesUpToNowAreAscii; + + return numAsciiBytes; + } + } + } +} diff --git a/src/Common/src/CoreLib/System/Text/ASCIIUtility.cs b/src/Common/src/CoreLib/System/Text/ASCIIUtility.cs index 755f92561033..8ff5b054290b 100644 --- a/src/Common/src/CoreLib/System/Text/ASCIIUtility.cs +++ b/src/Common/src/CoreLib/System/Text/ASCIIUtility.cs @@ -21,19 +21,20 @@ namespace System.Text { internal static partial class ASCIIUtility { - /// - /// Returns iff all bytes in are ASCII. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool AllBytesInUInt32AreAscii(uint value) +#if DEBUG + static ASCIIUtility() { - return ((value & 0x80808080u) == 0); + Debug.Assert(sizeof(nint) == IntPtr.Size && nint.MinValue < 0, "nint is defined incorrectly."); + Debug.Assert(sizeof(nuint) == IntPtr.Size && nuint.MinValue == 0, "nuint is defined incorrectly."); } +#endif // DEBUG [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool AllBytesInUInt64AreAscii(ulong value) { - return ((value & 0x80808080_80808080ul) == 0); + // If the high bit of any byte is set, that byte is non-ASCII. + + return ((value & UInt64HighBitsOnlyMask) == 0); } /// @@ -54,56 +55,6 @@ private static bool AllCharsInUInt64AreAscii(ulong value) return ((value & ~0x007F007F_007F007Ful) == 0); } - /// - /// Given a 24-bit integer which represents a three-byte buffer read in machine endianness, - /// counts the number of consecutive ASCII bytes starting from the beginning of the buffer. - /// Returns a value 0 - 3, inclusive. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint CountNumberOfLeadingAsciiBytesFrom24BitInteger(uint value) - { - // This implementation seems to have better performance than tzcnt. - - // The 'allBytesUpToNowAreAscii' DWORD uses bit twiddling to hold a 1 or a 0 depending - // on whether all processed bytes were ASCII. Then we accumulate all of the - // results to calculate how many consecutive ASCII bytes are present. - - value = ~value; - - if (BitConverter.IsLittleEndian) - { - // Read first byte - uint allBytesUpToNowAreAscii = (value >>= 7) & 1; - uint numAsciiBytes = allBytesUpToNowAreAscii; - - // Read second byte - allBytesUpToNowAreAscii &= (value >>= 8); - numAsciiBytes += allBytesUpToNowAreAscii; - - // Read third byte - allBytesUpToNowAreAscii &= (value >>= 8); - numAsciiBytes += allBytesUpToNowAreAscii; - - return numAsciiBytes; - } - else - { - // Read first byte - uint allBytesUpToNowAreAscii = (value = ROL32(value, 1)) & 1; - uint numAsciiBytes = allBytesUpToNowAreAscii; - - // Read second byte - allBytesUpToNowAreAscii &= (value = ROL32(value, 8)); - numAsciiBytes += allBytesUpToNowAreAscii; - - // Read third byte - allBytesUpToNowAreAscii &= (value = ROL32(value, 8)); - numAsciiBytes += allBytesUpToNowAreAscii; - - return numAsciiBytes; - } - } - /// /// Given a DWORD which represents two packed chars in machine-endian order, /// iff the first char (in machine-endian order) is ASCII. @@ -273,7 +224,7 @@ private static unsafe nuint GetIndexOfFirstNonAsciiByte_Default(byte* pBuffer, n // we get to the high byte; or (b) all of the earlier bytes are ASCII, so the high byte must be // non-ASCII. In both cases we only care about the low 24 bits. - pBuffer += CountNumberOfLeadingAsciiBytesFrom24BitInteger(currentUInt32); + pBuffer += CountNumberOfLeadingAsciiBytesFromUInt32WithSomeNonAsciiData(currentUInt32); goto Finish; } @@ -435,7 +386,7 @@ private static unsafe nuint GetIndexOfFirstNonAsciiByte_Sse2(byte* pBuffer, nuin uint currentDWord; Debug.Assert(!AllBytesInUInt32AreAscii(currentDWord), "Shouldn't be here unless we see non-ASCII data."); - pBuffer += CountNumberOfLeadingAsciiBytesFrom24BitInteger(currentDWord); + pBuffer += CountNumberOfLeadingAsciiBytesFromUInt32WithSomeNonAsciiData(currentDWord); goto Finish; @@ -461,7 +412,7 @@ private static unsafe nuint GetIndexOfFirstNonAsciiByte_Sse2(byte* pBuffer, nuin // Clear everything but the high bit of each byte, then tzcnt. // Remember the / 8 at the end to convert bit count to byte count. - candidateUInt64 &= 0x80808080_80808080ul; + candidateUInt64 &= UInt64HighBitsOnlyMask; pBuffer += (nuint)(Bmi1.X64.TrailingZeroCount(candidateUInt64) / 8); goto Finish; } @@ -1395,17 +1346,7 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA // Turn the 8 ASCII chars we just read into 8 ASCII bytes, then copy it to the destination. Vector128 asciiVector = Sse2.PackUnsignedSaturate(utf16VectorFirst, utf16VectorFirst); - - if (Sse41.X64.IsSupported) - { - // Use PEXTRQ instruction if available, since it can extract from the vector directly to the destination address. - Unsafe.WriteUnaligned(pAsciiBuffer, Sse41.X64.Extract(asciiVector.AsUInt64(), 0)); - } - else - { - // Bounce this through a temporary register (with potential stack spillage) before writing to memory. - Unsafe.WriteUnaligned(pAsciiBuffer, asciiVector.AsUInt64().GetElement(0)); - } + Sse2.StoreLow((ulong*)pAsciiBuffer, asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED nuint currentOffsetInElements = SizeOfVector128 / 2; // we processed 8 elements so far @@ -1444,16 +1385,7 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA // Turn the 8 ASCII chars we just read into 8 ASCII bytes, then copy it to the destination. asciiVector = Sse2.PackUnsignedSaturate(utf16VectorFirst, utf16VectorFirst); - - // See comments earlier in this method for information about how this works. - if (Sse41.X64.IsSupported) - { - Unsafe.WriteUnaligned(pAsciiBuffer + currentOffsetInElements, Sse41.X64.Extract(asciiVector.AsUInt64(), 0)); - } - else - { - Unsafe.WriteUnaligned(pAsciiBuffer + currentOffsetInElements, asciiVector.AsUInt64().GetElement(0)); - } + Sse2.StoreLow((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is UNALIGNED } // Calculate how many elements we wrote in order to get pAsciiBuffer to its next alignment @@ -1529,26 +1461,12 @@ private static unsafe nuint NarrowUtf16ToAscii_Sse2(char* pUtf16Buffer, byte* pA Debug.Assert(((nuint)pAsciiBuffer + currentOffsetInElements) % sizeof(ulong) == 0, "Destination should be ulong-aligned."); - // See comments earlier in this method for information about how this works. - if (Sse41.X64.IsSupported) - { - *(ulong*)(pAsciiBuffer + currentOffsetInElements) = Sse41.X64.Extract(asciiVector.AsUInt64(), 0); - } - else - { - *(ulong*)(pAsciiBuffer + currentOffsetInElements) = asciiVector.AsUInt64().GetElement(0); - } + Sse2.StoreLow((ulong*)(pAsciiBuffer + currentOffsetInElements), asciiVector.AsUInt64()); // ulong* calculated here is aligned currentOffsetInElements += SizeOfVector128 / 2; goto Finish; } - /// - /// Rotates a left. The JIT is smart enough to turn this into a ROL / ROR instruction. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static uint ROL32(uint value, int shift) => (value << shift) | (value >> (32 - shift)); - /// /// Copies as many ASCII bytes (00..7F) as possible from /// to , stopping when the first non-ASCII byte is encountered diff --git a/src/Common/src/CoreLib/System/Text/DecoderNLS.cs b/src/Common/src/CoreLib/System/Text/DecoderNLS.cs index 9040a94f0f6d..bb5aa5f0ac6b 100644 --- a/src/Common/src/CoreLib/System/Text/DecoderNLS.cs +++ b/src/Common/src/CoreLib/System/Text/DecoderNLS.cs @@ -266,6 +266,7 @@ internal int DrainLeftoverDataForGetCharCount(ReadOnlySpan bytes, out int // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown. Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in the fallback buffer."); + Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the decoder."); // Copy the existing leftover data plus as many bytes as possible of the new incoming data // into a temporary concated buffer, then get its char count by decoding it. @@ -319,6 +320,7 @@ internal int DrainLeftoverDataForGetChars(ReadOnlySpan bytes, Span c // to be in progress. Unlike EncoderNLS, this is simply a Debug.Assert. No exception is thrown. Debug.Assert(_fallbackBuffer is null || _fallbackBuffer.Remaining == 0, "Should have no data remaining in the fallback buffer."); + Debug.Assert(HasLeftoverData, "Caller shouldn't invoke this routine unless there's leftover data in the decoder."); // Copy the existing leftover data plus as many bytes as possible of the new incoming data // into a temporary concated buffer, then transcode it from bytes to chars. @@ -370,6 +372,14 @@ internal int DrainLeftoverDataForGetChars(ReadOnlySpan bytes, Span c Finish: + // Report back the number of bytes (from the new incoming span) we consumed just now. + // This calculation is simple: it's the difference between the original leftover byte + // count and the number of bytes from the combined buffer we needed to decode the first + // scalar value. We need to report this before the call to SetLeftoverData / + // ClearLeftoverData because those methods will overwrite the _leftoverByteCount field. + + bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount; + if (persistNewCombinedBuffer) { Debug.Assert(combinedBufferBytesConsumed == combinedBuffer.Length, "We should be asked to persist the entire combined buffer."); @@ -380,7 +390,6 @@ internal int DrainLeftoverDataForGetChars(ReadOnlySpan bytes, Span c ClearLeftoverData(); // the buffer contains no partial data; we'll go down the normal paths } - bytesConsumed = combinedBufferBytesConsumed - _leftoverByteCount; // amount of 'bytes' buffer consumed just now return charsWritten; DestinationTooSmall: diff --git a/src/Common/src/CoreLib/System/Text/Encoding.Internal.cs b/src/Common/src/CoreLib/System/Text/Encoding.Internal.cs index 0e321679579c..ca740a1adc8d 100644 --- a/src/Common/src/CoreLib/System/Text/Encoding.Internal.cs +++ b/src/Common/src/CoreLib/System/Text/Encoding.Internal.cs @@ -850,8 +850,14 @@ private unsafe int GetCharCountWithFallback(byte* pOriginalBytes, int originalBy ReadOnlySpan bytes = new ReadOnlySpan(pOriginalBytes, originalByteCount).Slice(bytesConsumedSoFar); - int totalCharCount = decoder.DrainLeftoverDataForGetCharCount(bytes, out int bytesConsumedJustNow); - bytes = bytes.Slice(bytesConsumedJustNow); + int bytesConsumedJustNow = 0; + int totalCharCount = 0; + + if (decoder.HasLeftoverData) + { + totalCharCount = decoder.DrainLeftoverDataForGetCharCount(bytes, out bytesConsumedJustNow); + bytes = bytes.Slice(bytesConsumedJustNow); + } // Now try invoking the "fast path" (no fallback) implementation. // We can use Unsafe.AsPointer here since these spans are created from pinned data (raw pointers). @@ -1120,10 +1126,15 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig ReadOnlySpan bytes = new ReadOnlySpan(pOriginalBytes, originalByteCount).Slice(bytesConsumedSoFar); Span chars = new Span(pOriginalChars, originalCharCount).Slice(charsWrittenSoFar); - int charsWrittenJustNow = decoder.DrainLeftoverDataForGetChars(bytes, chars, out int bytesConsumedJustNow); + int bytesConsumedJustNow = 0; + int charsWrittenJustNow = 0; - bytes = bytes.Slice(bytesConsumedJustNow); - chars = chars.Slice(charsWrittenJustNow); + if (decoder.HasLeftoverData) + { + charsWrittenJustNow = decoder.DrainLeftoverDataForGetChars(bytes, chars, out bytesConsumedJustNow); + bytes = bytes.Slice(bytesConsumedJustNow); + chars = chars.Slice(charsWrittenJustNow); + } Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0, "Should be no remaining fallback data at this point."); diff --git a/src/Common/src/CoreLib/System/Text/Rune.cs b/src/Common/src/CoreLib/System/Text/Rune.cs index a91c0fcb9950..00179bf56d72 100644 --- a/src/Common/src/CoreLib/System/Text/Rune.cs +++ b/src/Common/src/CoreLib/System/Text/Rune.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; +using System.Text.Unicode; namespace System.Text { @@ -662,46 +663,6 @@ public static OperationStatus DecodeLastFromUtf8(ReadOnlySpan source, out } } - public static OperationStatus DecodeUtf16(ReadOnlySpan utf16Source, out Rune result, out int charsConsumed) - { - // [TODO] This method was renamed to DecodeFromUtf16. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return DecodeFromUtf16(utf16Source, out result, out charsConsumed); - } - - public static OperationStatus DecodeUtf16FromEnd(ReadOnlySpan utf16Source, out Rune result, out int charsConsumed) - { - // [TODO] This method was renamed to DecodeLastFromUtf16. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return DecodeLastFromUtf16(utf16Source, out result, out charsConsumed); - } - - public static OperationStatus DecodeUtf8(ReadOnlySpan utf8Source, out Rune result, out int bytesConsumed) - { - // [TODO] This method was renamed to DecodeFromUtf8. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return DecodeFromUtf8(utf8Source, out result, out bytesConsumed); - } - - public static OperationStatus DecodeUtf8FromEnd(ReadOnlySpan utf8Source, out Rune result, out int bytesConsumed) - { - // [TODO] This method was renamed to DecodeLastFromUtf8. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return DecodeLastFromUtf8(utf8Source, out result, out bytesConsumed); - } - /// /// Encodes this to a UTF-16 destination buffer. /// @@ -984,16 +945,6 @@ public bool TryEncodeToUtf16(Span destination, out int charsWritten) return false; } - public bool TryEncode(Span destination, out int charsWritten) - { - // [TODO] This method was renamed to TryEncodeToUtf16. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return TryEncodeToUtf16(destination, out charsWritten); - } - /// /// Encodes this to a destination buffer as UTF-8 bytes. /// @@ -1062,16 +1013,6 @@ public bool TryEncodeToUtf8(Span destination, out int bytesWritten) return false; } - public bool TryEncodeToUtf8Bytes(Span destination, out int bytesWritten) - { - // [TODO] This method was renamed to TryEncodeToUtf8. We'll leave this copy of - // the method here temporarily so that we don't break corefx consumers - // while the rename takes place. - // Tracking issue: https://github.com/dotnet/coreclr/issues/23319 - - return TryEncodeToUtf8(destination, out bytesWritten); - } - /// /// Attempts to get the which begins at index in /// string . diff --git a/src/Common/src/CoreLib/System/Text/UTF8Encoding.cs b/src/Common/src/CoreLib/System/Text/UTF8Encoding.cs index aaac975ec887..b5817447c7df 100644 --- a/src/Common/src/CoreLib/System/Text/UTF8Encoding.cs +++ b/src/Common/src/CoreLib/System/Text/UTF8Encoding.cs @@ -10,14 +10,12 @@ // The fast loops attempts to blaze through as fast as possible with optimistic range checks, // processing multiple characters at a time, and falling back to the slow loop for all special cases. -// This define can be used to turn off the fast loops. Useful for finding whether -// the problem is fastloop-specific. -#define FASTLOOP - using System; +using System.Buffers; using System.Diagnostics; -using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text.Unicode; namespace System.Text { @@ -129,22 +127,26 @@ internal sealed override void SetDefaultFallbacks() public override unsafe int GetByteCount(char[] chars, int index, int count) { // Validate input parameters - if (chars == null) - throw new ArgumentNullException(nameof(chars), SR.ArgumentNull_Array); - if (index < 0 || count < 0) - throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum); + if (chars is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.chars, ExceptionResource.ArgumentNull_Array); + } - if (chars.Length - index < count) - throw new ArgumentOutOfRangeException(nameof(chars), SR.ArgumentOutOfRange_IndexCountBuffer); + if ((index | count) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException((index < 0) ? ExceptionArgument.index : ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - // If no input, return 0, avoid fixed empty array problem - if (count == 0) - return 0; + if (chars.Length - index < count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.chars, ExceptionResource.ArgumentOutOfRange_IndexCountBuffer); + } - // Just call the pointer version fixed (char* pChars = chars) - return GetByteCount(pChars + index, count, null); + { + return GetByteCountCommon(pChars + index, count); + } } // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) @@ -154,12 +156,17 @@ public override unsafe int GetByteCount(char[] chars, int index, int count) public override unsafe int GetByteCount(string chars) { - // Validate input - if (chars==null) - throw new ArgumentNullException("s"); + // Validate input parameters + + if (chars is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.chars); + } fixed (char* pChars = chars) - return GetByteCount(pChars, chars.Length, null); + { + return GetByteCountCommon(pChars, chars.Length); + } } // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) @@ -170,22 +177,78 @@ public override unsafe int GetByteCount(string chars) public override unsafe int GetByteCount(char* chars, int count) { // Validate Parameters + if (chars == null) - throw new ArgumentNullException(nameof(chars), SR.ArgumentNull_Array); + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.chars); + } if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - // Call it with empty encoder - return GetByteCount(chars, count, null); + return GetByteCountCommon(chars, count); } public override unsafe int GetByteCount(ReadOnlySpan chars) { - fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) + // It's ok for us to pass null pointers down to the workhorse below. + + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + { + return GetByteCountCommon(charsPtr, chars.Length); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetByteCountCommon(char* pChars, int charCount) + { + // Common helper method for all non-EncoderNLS entry points to GetByteCount. + // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. + + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); + + // First call into the fast path. + // Don't bother providing a fallback mechanism; our fast path doesn't use it. + + int totalByteCount = GetByteCountFast(pChars, charCount, fallback: null, out int charsConsumed); + + if (charsConsumed != charCount) + { + // If there's still data remaining in the source buffer, go down the fallback path. + // We need to check for integer overflow since the fallback could change the required + // output count in unexpected ways. + + totalByteCount += GetByteCountWithFallback(pChars, charCount, charsConsumed); + if (totalByteCount < 0) + { + ThrowConversionOverflow(); + } + } + + return totalByteCount; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] // called directly by GetCharCountCommon + private protected sealed override unsafe int GetByteCountFast(char* pChars, int charsLength, EncoderFallback fallback, out int charsConsumed) + { + // The number of UTF-8 code units may exceed the number of UTF-16 code units, + // so we'll need to check for overflow before casting to Int32. + + char* ptrToFirstInvalidChar = Utf16Utility.GetPointerToFirstInvalidChar(pChars, charsLength, out long utf8CodeUnitCountAdjustment, out _); + + int tempCharsConsumed = (int)(ptrToFirstInvalidChar - pChars); + charsConsumed = tempCharsConsumed; + + long totalUtf8Bytes = tempCharsConsumed + utf8CodeUnitCountAdjustment; + if ((ulong)totalUtf8Bytes > int.MaxValue) { - return GetByteCount(charsPtr, chars.Length, baseEncoder: null); + ThrowConversionOverflow(); } + + return (int)totalUtf8Bytes; } // Parent method is safe. @@ -196,22 +259,37 @@ public override unsafe int GetByteCount(ReadOnlySpan chars) public override unsafe int GetBytes(string s, int charIndex, int charCount, byte[] bytes, int byteIndex) { - if (s == null || bytes == null) - throw new ArgumentNullException((s == null ? nameof(s) : nameof(bytes)), SR.ArgumentNull_Array); + // Validate Parameters - if (charIndex < 0 || charCount < 0) - throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)), SR.ArgumentOutOfRange_NeedNonNegNum); + if (s is null || bytes is null) + { + ThrowHelper.ThrowArgumentNullException( + argument: (s is null) ? ExceptionArgument.s : ExceptionArgument.bytes, + resource: ExceptionResource.ArgumentNull_Array); + } - if (s.Length - charIndex < charCount) - throw new ArgumentOutOfRangeException(nameof(s), SR.ArgumentOutOfRange_IndexCount); + if ((charIndex | charCount) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (charIndex < 0) ? ExceptionArgument.charIndex : ExceptionArgument.charCount, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - if (byteIndex < 0 || byteIndex > bytes.Length) - throw new ArgumentOutOfRangeException(nameof(byteIndex), SR.ArgumentOutOfRange_Index); + if (s.Length - charIndex < charCount) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.s, ExceptionResource.ArgumentOutOfRange_IndexCount); + } - int byteCount = bytes.Length - byteIndex; + if ((uint)byteIndex > bytes.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.byteIndex, ExceptionResource.ArgumentOutOfRange_Index); + } - fixed (char* pChars = s) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span)bytes)) - return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null); + fixed (char* pChars = s) + fixed (byte* pBytes = bytes) + { + return GetBytesCommon(pChars + charIndex, charCount, pBytes + byteIndex, bytes.Length - byteIndex); + } } // Encodes a range of characters in a character array into a range of bytes @@ -232,28 +310,36 @@ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) { // Validate parameters - if (chars == null || bytes == null) - throw new ArgumentNullException((chars == null ? nameof(chars) : nameof(bytes)), SR.ArgumentNull_Array); - if (charIndex < 0 || charCount < 0) - throw new ArgumentOutOfRangeException((charIndex < 0 ? nameof(charIndex) : nameof(charCount)), SR.ArgumentOutOfRange_NeedNonNegNum); - - if (chars.Length - charIndex < charCount) - throw new ArgumentOutOfRangeException(nameof(chars), SR.ArgumentOutOfRange_IndexCountBuffer); + if (chars is null || bytes is null) + { + ThrowHelper.ThrowArgumentNullException( + argument: (chars is null) ? ExceptionArgument.chars : ExceptionArgument.bytes, + resource: ExceptionResource.ArgumentNull_Array); + } - if (byteIndex < 0 || byteIndex > bytes.Length) - throw new ArgumentOutOfRangeException(nameof(byteIndex), SR.ArgumentOutOfRange_Index); + if ((charIndex | charCount) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (charIndex < 0) ? ExceptionArgument.charIndex : ExceptionArgument.charCount, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - // If nothing to encode return 0, avoid fixed problem - if (charCount == 0) - return 0; + if (chars.Length - charIndex < charCount) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.chars, ExceptionResource.ArgumentOutOfRange_IndexCount); + } - // Just call pointer version - int byteCount = bytes.Length - byteIndex; + if ((uint)byteIndex > bytes.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.byteIndex, ExceptionResource.ArgumentOutOfRange_Index); + } - fixed (char* pChars = chars) fixed (byte* pBytes = &MemoryMarshal.GetReference((Span)bytes)) - // Remember that byteCount is # to decode, not size of array. - return GetBytes(pChars + charIndex, charCount, pBytes + byteIndex, byteCount, null); + fixed (char* pChars = chars) + fixed (byte* pBytes = bytes) + { + return GetBytesCommon(pChars + charIndex, charCount, pBytes + byteIndex, bytes.Length - byteIndex); + } } // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) @@ -264,24 +350,77 @@ public override unsafe int GetBytes(char[] chars, int charIndex, int charCount, public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount) { // Validate Parameters - if (bytes == null || chars == null) - throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars), SR.ArgumentNull_Array); - if (charCount < 0 || byteCount < 0) - throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)), SR.ArgumentOutOfRange_NeedNonNegNum); + if (chars == null || bytes == null) + { + ThrowHelper.ThrowArgumentNullException( + argument: (chars is null) ? ExceptionArgument.chars : ExceptionArgument.bytes, + resource: ExceptionResource.ArgumentNull_Array); + } + + if ((charCount | byteCount) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (charCount < 0) ? ExceptionArgument.charCount : ExceptionArgument.byteCount, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - return GetBytes(chars, charCount, bytes, byteCount, null); + return GetBytesCommon(chars, charCount, bytes, byteCount); } public override unsafe int GetBytes(ReadOnlySpan chars, Span bytes) { - fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) - fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) + // It's ok for us to operate on null / empty spans. + + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) { - return GetBytes(charsPtr, chars.Length, bytesPtr, bytes.Length, baseEncoder: null); + return GetBytesCommon(charsPtr, chars.Length, bytesPtr, bytes.Length); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetBytesCommon(char* pChars, int charCount, byte* pBytes, int byteCount) + { + // Common helper method for all non-EncoderNLS entry points to GetBytes. + // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. + + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); + + // First call into the fast path. + + int bytesWritten = GetBytesFast(pChars, charCount, pBytes, byteCount, out int charsConsumed); + + if (charsConsumed == charCount) + { + // All elements converted - return immediately. + + return bytesWritten; + } + else + { + // Simple narrowing conversion couldn't operate on entire buffer - invoke fallback. + + return GetBytesWithFallback(pChars, charCount, pBytes, byteCount, charsConsumed, bytesWritten); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] // called directly by GetBytesCommon + private protected sealed override unsafe int GetBytesFast(char* pChars, int charsLength, byte* pBytes, int bytesLength, out int charsConsumed) + { + // We don't care about the exact OperationStatus value returned by the workhorse routine; we only + // care if the workhorse was able to consume the entire input payload. If we're unable to do so, + // we'll handle the remainder in the fallback routine. + + Utf8Utility.TranscodeToUtf8(pChars, charsLength, pBytes, bytesLength, out char* pInputBufferRemaining, out byte* pOutputBufferRemaining); + + charsConsumed = (int)(pInputBufferRemaining - pChars); + return (int)(pOutputBufferRemaining - pBytes); + } + // Returns the number of characters produced by decoding a range of bytes // in a byte array. // @@ -293,22 +432,26 @@ public override unsafe int GetBytes(ReadOnlySpan chars, Span bytes) public override unsafe int GetCharCount(byte[] bytes, int index, int count) { // Validate Parameters - if (bytes == null) - throw new ArgumentNullException(nameof(bytes), SR.ArgumentNull_Array); - if (index < 0 || count < 0) - throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum); + if (bytes is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.bytes, ExceptionResource.ArgumentNull_Array); + } - if (bytes.Length - index < count) - throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer); + if ((index | count) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException((index < 0) ? ExceptionArgument.index : ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - // If no input just return 0, fixed doesn't like 0 length arrays. - if (count == 0) - return 0; + if (bytes.Length - index < count) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.bytes, ExceptionResource.ArgumentOutOfRange_IndexCountBuffer); + } - // Just call pointer version fixed (byte* pBytes = bytes) - return GetCharCount(pBytes + index, count, null); + { + return GetCharCountCommon(pBytes + index, count); + } } // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) @@ -319,20 +462,27 @@ public override unsafe int GetCharCount(byte[] bytes, int index, int count) public override unsafe int GetCharCount(byte* bytes, int count) { // Validate Parameters + if (bytes == null) - throw new ArgumentNullException(nameof(bytes), SR.ArgumentNull_Array); + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.bytes, ExceptionResource.ArgumentNull_Array); + } if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.count, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - return GetCharCount(bytes, count, null); + return GetCharCountCommon(bytes, count); } public override unsafe int GetCharCount(ReadOnlySpan bytes) { - fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) + // It's ok for us to pass null pointers down to the workhorse routine. + + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) { - return GetCharCount(bytesPtr, bytes.Length, baseDecoder: null); + return GetCharCountCommon(bytesPtr, bytes.Length); } } @@ -345,28 +495,36 @@ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { // Validate Parameters - if (bytes == null || chars == null) - throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars), SR.ArgumentNull_Array); - if (byteIndex < 0 || byteCount < 0) - throw new ArgumentOutOfRangeException((byteIndex < 0 ? nameof(byteIndex) : nameof(byteCount)), SR.ArgumentOutOfRange_NeedNonNegNum); - - if ( bytes.Length - byteIndex < byteCount) - throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer); + if (bytes is null || chars is null) + { + ThrowHelper.ThrowArgumentNullException( + argument: (bytes is null) ? ExceptionArgument.bytes : ExceptionArgument.chars, + resource: ExceptionResource.ArgumentNull_Array); + } - if (charIndex < 0 || charIndex > chars.Length) - throw new ArgumentOutOfRangeException(nameof(charIndex), SR.ArgumentOutOfRange_Index); + if ((byteIndex | byteCount) < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (byteIndex < 0) ? ExceptionArgument.byteIndex : ExceptionArgument.byteCount, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } - // If no input, return 0 & avoid fixed problem - if (byteCount == 0) - return 0; + if (bytes.Length - byteIndex < byteCount) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.bytes, ExceptionResource.ArgumentOutOfRange_IndexCountBuffer); + } - // Just call pointer version - int charCount = chars.Length - charIndex; + if ((uint)charIndex > (uint)chars.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.charIndex, ExceptionResource.ArgumentOutOfRange_Index); + } - fixed (byte* pBytes = bytes) fixed (char* pChars = &MemoryMarshal.GetReference((Span)chars)) - // Remember that charCount is # to decode, not size of array - return GetChars(pBytes + byteIndex, byteCount, pChars + charIndex, charCount, null); + fixed (byte* pBytes = bytes) + fixed (char* pChars = chars) + { + return GetCharsCommon(pBytes + byteIndex, byteCount, pChars + charIndex, chars.Length - charIndex); + } } // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) @@ -377,2120 +535,245 @@ public override unsafe int GetChars(byte[] bytes, int byteIndex, int byteCount, public unsafe override int GetChars(byte* bytes, int byteCount, char* chars, int charCount) { // Validate Parameters - if (bytes == null || chars == null) - throw new ArgumentNullException(bytes == null ? nameof(bytes) : nameof(chars), SR.ArgumentNull_Array); - if (charCount < 0 || byteCount < 0) - throw new ArgumentOutOfRangeException((charCount < 0 ? nameof(charCount) : nameof(byteCount)), SR.ArgumentOutOfRange_NeedNonNegNum); - - return GetChars(bytes, byteCount, chars, charCount, null); - } + if (bytes is null || chars is null) + { + ThrowHelper.ThrowArgumentNullException( + argument: (bytes is null) ? ExceptionArgument.bytes : ExceptionArgument.chars, + resource: ExceptionResource.ArgumentNull_Array); + } - public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) - { - fixed (byte* bytesPtr = &MemoryMarshal.GetNonNullPinnableReference(bytes)) - fixed (char* charsPtr = &MemoryMarshal.GetNonNullPinnableReference(chars)) + if ((byteCount | charCount) < 0) { - return GetChars(bytesPtr, bytes.Length, charsPtr, chars.Length, baseDecoder: null); + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (byteCount < 0) ? ExceptionArgument.byteCount : ExceptionArgument.charCount, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); } - } - // Returns a string containing the decoded representation of a range of - // bytes in a byte array. - // - // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) - // So if you fix this, fix the others. Currently those include: - // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding - // parent method is safe + return GetCharsCommon(bytes, byteCount, chars, charCount); + } - public override unsafe string GetString(byte[] bytes, int index, int count) + public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) { - // Validate Parameters - if (bytes == null) - throw new ArgumentNullException(nameof(bytes), SR.ArgumentNull_Array); + // It's ok for us to pass null pointers down to the workhorse below. - if (index < 0 || count < 0) - throw new ArgumentOutOfRangeException((index < 0 ? nameof(index) : nameof(count)), SR.ArgumentOutOfRange_NeedNonNegNum); - - if (bytes.Length - index < count) - throw new ArgumentOutOfRangeException(nameof(bytes), SR.ArgumentOutOfRange_IndexCountBuffer); - - // Avoid problems with empty input buffer - if (count == 0) return string.Empty; - - fixed (byte* pBytes = bytes) - return string.CreateStringFromEncoding( - pBytes + index, count, this); + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + { + return GetCharsCommon(bytesPtr, bytes.Length, charsPtr, chars.Length); + } } + // WARNING: If we throw an error, then System.Resources.ResourceReader calls this method. + // So if we're really broken, then that could also throw an error... recursively. + // So try to make sure GetChars can at least process all uses by + // System.Resources.ResourceReader! // - // End of standard methods copied from EncodingNLS.cs - // - - // To simplify maintenance, the structure of GetByteCount and GetBytes should be - // kept the same as much as possible - internal sealed override unsafe int GetByteCount(char* chars, int count, EncoderNLS baseEncoder) + // Note: We throw exceptions on individually encoded surrogates and other non-shortest forms. + // If exceptions aren't turned on, then we drop all non-shortest &individual surrogates. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int charCount) { - // For fallback we may need a fallback buffer. - // We wait to initialize it though in case we don't have any broken input unicode - EncoderFallbackBuffer fallbackBuffer = null; - char* pSrcForFallback; + // Common helper method for all non-DecoderNLS entry points to GetChars. + // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - char* pSrc = chars; - char* pEnd = pSrc + count; + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); + Debug.Assert(charCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pChars != null || charCount == 0, "Input pointer shouldn't be null if non-zero length specified."); - // Start by assuming we have as many as count - int byteCount = count; + // First call into the fast path. - int ch = 0; + int charsWritten = GetCharsFast(pBytes, byteCount, pChars, charCount, out int bytesConsumed); - if (baseEncoder != null) + if (bytesConsumed == byteCount) { - UTF8Encoder encoder = (UTF8Encoder)baseEncoder; - ch = encoder.surrogateChar; - - // We mustn't have left over fallback data when counting - if (encoder.InternalHasFallbackBuffer) - { - fallbackBuffer = encoder.FallbackBuffer; - if (fallbackBuffer.Remaining > 0) - throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType())); + // All elements converted - return immediately. - // Set our internal fallback interesting things. - fallbackBuffer.InternalInitialize(chars, pEnd, encoder, false); - } + return charsWritten; } - - for (;;) + else { - // SLOWLOOP: does all range checks, handles all special cases, but it is slow - if (pSrc >= pEnd) - { - if (ch == 0) - { - // Unroll any fallback that happens at the end - ch = fallbackBuffer != null ? fallbackBuffer.InternalGetNextChar() : 0; - if (ch > 0) - { - byteCount++; - goto ProcessChar; - } - } - else - { - // Case of surrogates in the fallback. - if (fallbackBuffer != null && fallbackBuffer.bFallingBack) - { - Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF, - "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture)); - - ch = fallbackBuffer.InternalGetNextChar(); - byteCount++; - - if (InRange(ch, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - ch = 0xfffd; - byteCount++; - goto EncodeChar; - } - else if (ch > 0) - { - goto ProcessChar; - } - else - { - byteCount--; // ignore last one. - break; - } - } - } - - if (ch <= 0) - { - break; - } - if (baseEncoder != null && !baseEncoder.MustFlush) - { - break; - } - - // attempt to encode the partial surrogate (will fallback or ignore it), it'll also subtract 1. - byteCount++; - goto EncodeChar; - } + // Simple narrowing conversion couldn't operate on entire buffer - invoke fallback. - if (ch > 0) - { - Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF, - "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture)); - - // use separate helper variables for local contexts so that the jit optimizations - // won't get confused about the variable lifetimes - int cha = *pSrc; - - // count the pending surrogate - byteCount++; - - // In previous byte, we encountered a high surrogate, so we are expecting a low surrogate here. - // if (IsLowSurrogate(cha)) { - if (InRange(cha, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // Don't need a real # because we're just counting, anything > 0x7ff ('cept surrogate) will do. - ch = 0xfffd; - // ch = cha + (ch << 10) + - // (0x10000 - // - CharUnicodeInfo.LOW_SURROGATE_START - // - (CharUnicodeInfo.HIGH_SURROGATE_START << 10) ); - - // Use this next char - pSrc++; - } - // else ch is still high surrogate and encoding will fail (so don't add count) - - // attempt to encode the surrogate or partial surrogate - goto EncodeChar; - } - - // If we've used a fallback, then we have to check for it - if (fallbackBuffer != null) - { - ch = fallbackBuffer.InternalGetNextChar(); - if (ch > 0) - { - // We have an extra byte we weren't expecting. - byteCount++; - goto ProcessChar; - } - } - - // read next char. The JIT optimization seems to be getting confused when - // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead - ch = *pSrc; - pSrc++; - - ProcessChar: - // if (IsHighSurrogate(ch)) { - if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END)) - { - // we will count this surrogate next time around - byteCount--; - continue; - } - // either good char or partial surrogate - - EncodeChar: - // throw exception on partial surrogate if necessary - // if (IsLowSurrogate(ch) || IsHighSurrogate(ch)) - if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // Lone surrogates aren't allowed - // Have to make a fallback buffer if we don't have one - if (fallbackBuffer == null) - { - // wait on fallbacks if we can - // For fallback we may need a fallback buffer - if (baseEncoder == null) - fallbackBuffer = this.encoderFallback.CreateFallbackBuffer(); - else - fallbackBuffer = baseEncoder.FallbackBuffer; - - // Set our internal fallback interesting things. - fallbackBuffer.InternalInitialize(chars, chars + count, baseEncoder, false); - } - - // Do our fallback. Actually we already know its a mixed up surrogate, - // so the ref pSrc isn't gonna do anything. - pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered - fallbackBuffer.InternalFallback(unchecked((char)ch), ref pSrcForFallback); - pSrc = pSrcForFallback; - - // Ignore it if we don't throw (we had preallocated this ch) - byteCount--; - ch = 0; - continue; - } - - // Count them - if (ch > 0x7F) - { - if (ch > 0x7FF) - { - // the extra surrogate byte was compensated by the second surrogate character - // (2 surrogates make 4 bytes. We've already counted 2 bytes, 1 per char) - byteCount++; - } - byteCount++; - } - -#if BIT64 - // check for overflow - if (byteCount < 0) - { - break; - } -#endif - -#if FASTLOOP - // If still have fallback don't do fast loop - if (fallbackBuffer != null && (ch = fallbackBuffer.InternalGetNextChar()) != 0) - { - // We're reserving 1 byte for each char by default - byteCount++; - goto ProcessChar; - } - - int availableChars = PtrDiff(pEnd, pSrc); - - // don't fall into the fast decoding loop if we don't have enough characters - if (availableChars <= 13) - { - // try to get over the remainder of the ascii characters fast though - char* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered - while (pSrc < pLocalEnd) - { - ch = *pSrc; - pSrc++; - if (ch > 0x7F) - goto ProcessChar; - } - - // we are done - break; - } - -#if BIT64 - // make sure that we won't get a silent overflow inside the fast loop - // (Fall out to slow loop if we have this many characters) - availableChars &= 0x0FFFFFFF; -#endif - - // To compute the upper bound, assume that all characters are ASCII characters at this point, - // the boundary will be decreased for every non-ASCII character we encounter - // Also, we need 3 + 4 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates - char* pStop = pSrc + availableChars - (3 + 4); - - while (pSrc < pStop) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) // Not ASCII - { - if (ch > 0x7FF) // Not 2 Byte - { - if ((ch & 0xF800) == 0xD800) // See if its a Surrogate - goto LongCode; - byteCount++; - } - byteCount++; - } - - // get pSrc aligned - if ((unchecked((int)pSrc) & 0x2) != 0) - { - ch = *pSrc; - pSrc++; - if (ch > 0x7F) // Not ASCII - { - if (ch > 0x7FF) // Not 2 Byte - { - if ((ch & 0xF800) == 0xD800) // See if its a Surrogate - goto LongCode; - byteCount++; - } - byteCount++; - } - } - - // Run 2 * 4 characters at a time! - while (pSrc < pStop) - { - ch = *(int*)pSrc; - int chc = *(int*)(pSrc + 2); - if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) // See if not ASCII - { - if (((ch | chc) & unchecked((int)0xF800F800)) != 0) // See if not 2 Byte - { - goto LongCodeWithMask; - } - - - if ((ch & unchecked((int)0xFF800000)) != 0) // Actually 0x07800780 is all we care about (4 bits) - byteCount++; - if ((ch & unchecked((int)0xFF80)) != 0) - byteCount++; - if ((chc & unchecked((int)0xFF800000)) != 0) - byteCount++; - if ((chc & unchecked((int)0xFF80)) != 0) - byteCount++; - } - pSrc += 4; - - ch = *(int*)pSrc; - chc = *(int*)(pSrc + 2); - if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) // See if not ASCII - { - if (((ch | chc) & unchecked((int)0xF800F800)) != 0) // See if not 2 Byte - { - goto LongCodeWithMask; - } - - if ((ch & unchecked((int)0xFF800000)) != 0) - byteCount++; - if ((ch & unchecked((int)0xFF80)) != 0) - byteCount++; - if ((chc & unchecked((int)0xFF800000)) != 0) - byteCount++; - if ((chc & unchecked((int)0xFF80)) != 0) - byteCount++; - } - pSrc += 4; - } - break; - - LongCodeWithMask: - if (BitConverter.IsLittleEndian) - { - ch = (char)ch; - } - else - { - // be careful about the sign extension - ch = (int)(((uint)ch) >> 16); - } - pSrc++; - - if (ch <= 0x7F) - { - continue; - } - - LongCode: - // use separate helper variables for slow and fast loop so that the jit optimizations - // won't get confused about the variable lifetimes - if (ch > 0x7FF) - { - // if (IsLowSurrogate(ch) || IsHighSurrogate(ch)) - if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // 4 byte encoding - high surrogate + low surrogate - - int chd = *pSrc; - if ( - // !IsHighSurrogate(ch) // low without high -> bad - ch > CharUnicodeInfo.HIGH_SURROGATE_END || - // !IsLowSurrogate(chd) // high not followed by low -> bad - !InRange(chd, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // Back up and drop out to slow loop to figure out error - pSrc--; - break; - } - pSrc++; - - // byteCount - this byte is compensated by the second surrogate character - } - byteCount++; - } - byteCount++; - - // byteCount - the last byte is already included - } -#endif // FASTLOOP - - // no pending char at this point - ch = 0; + return GetCharsWithFallback(pBytes, byteCount, pChars, charCount, bytesConsumed, charsWritten); } - -#if BIT64 - // check for overflow - if (byteCount < 0) - { - throw new ArgumentException( - SR.Argument_ConversionOverflow); - } -#endif - - Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0, - "[UTF8Encoding.GetByteCount]Expected Empty fallback buffer"); - - return byteCount; } - // diffs two char pointers using unsigned arithmetic. The unsigned arithmetic - // is good enough for us, and it tends to generate better code than the signed - // arithmetic generated by default - private static unsafe int PtrDiff(char* a, char* b) + [MethodImpl(MethodImplOptions.AggressiveInlining)] // called directly by GetCharsCommon + private protected sealed override unsafe int GetCharsFast(byte* pBytes, int bytesLength, char* pChars, int charsLength, out int bytesConsumed) { - return (int)(((uint)((byte*)a - (byte*)b)) >> 1); - } + // We don't care about the exact OperationStatus value returned by the workhorse routine; we only + // care if the workhorse was able to consume the entire input payload. If we're unable to do so, + // we'll handle the remainder in the fallback routine. - // byte* flavor just for parity - private static unsafe int PtrDiff(byte* a, byte* b) - { - return (int)(a - b); - } + Utf8Utility.TranscodeToUtf16(pBytes, bytesLength, pChars, charsLength, out byte* pInputBufferRemaining, out char* pOutputBufferRemaining); - private static bool InRange(int ch, int start, int end) - { - return (uint)(ch - start) <= (uint)(end - start); + bytesConsumed = (int)(pInputBufferRemaining - pBytes); + return (int)(pOutputBufferRemaining - pChars); } - // Our workhorse - // Note: We ignore mismatched surrogates, unless the exception flag is set in which case we throw - internal sealed override unsafe int GetBytes( - char* chars, int charCount, byte* bytes, int byteCount, EncoderNLS baseEncoder) + private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS decoder) { - Debug.Assert(chars != null, "[UTF8Encoding.GetBytes]chars!=null"); - Debug.Assert(byteCount >= 0, "[UTF8Encoding.GetBytes]byteCount >=0"); - Debug.Assert(charCount >= 0, "[UTF8Encoding.GetBytes]charCount >=0"); - Debug.Assert(bytes != null, "[UTF8Encoding.GetBytes]bytes!=null"); - - UTF8Encoder encoder = null; - - // For fallback we may need a fallback buffer. - // We wait to initialize it though in case we don't have any broken input unicode - EncoderFallbackBuffer fallbackBuffer = null; - char* pSrcForFallback; - - char* pSrc = chars; - byte* pTarget = bytes; - - char* pEnd = pSrc + charCount; - byte* pAllocatedBufferEnd = pTarget + byteCount; - - int ch = 0; - - // assume that JIT will en-register pSrc, pTarget and ch + // We special-case DecoderReplacementFallback if it's telling us to write a single U+FFFD char, + // since we believe this to be relatively common and we can handle it more efficiently than + // the base implementation. - if (baseEncoder != null) + if (((decoder is null) ? this.DecoderFallback : decoder.Fallback) is DecoderReplacementFallback replacementFallback + && replacementFallback.MaxCharCount == 1 + && replacementFallback.DefaultString[0] == UnicodeUtility.ReplacementChar) { - encoder = (UTF8Encoder)baseEncoder; - ch = encoder.surrogateChar; + // Don't care about the exact OperationStatus, just how much of the payload we were able + // to process. - // We mustn't have left over fallback data when counting - if (encoder.InternalHasFallbackBuffer) - { - // We always need the fallback buffer in get bytes so we can flush any remaining ones if necessary - fallbackBuffer = encoder.FallbackBuffer; - if (fallbackBuffer.Remaining > 0 && encoder._throwOnOverflow) - throw new ArgumentException(SR.Format(SR.Argument_EncoderFallbackNotEmpty, this.EncodingName, encoder.Fallback.GetType())); - - // Set our internal fallback interesting things. - fallbackBuffer.InternalInitialize(chars, pEnd, encoder, true); - } - } - - for (;;) - { - // SLOWLOOP: does all range checks, handles all special cases, but it is slow - - if (pSrc >= pEnd) - { - if (ch == 0) - { - // Check if there's anything left to get out of the fallback buffer - ch = fallbackBuffer != null ? fallbackBuffer.InternalGetNextChar() : 0; - if (ch > 0) - { - goto ProcessChar; - } - } - else - { - // Case of leftover surrogates in the fallback buffer - if (fallbackBuffer != null && fallbackBuffer.bFallingBack) - { - Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF, - "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture)); - - int cha = ch; - - ch = fallbackBuffer.InternalGetNextChar(); - - if (InRange(ch, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - ch = ch + (cha << 10) + (0x10000 - CharUnicodeInfo.LOW_SURROGATE_START - (CharUnicodeInfo.HIGH_SURROGATE_START << 10)); - goto EncodeChar; - } - else if (ch > 0) - { - goto ProcessChar; - } - else - { - break; - } - } - } - - // attempt to encode the partial surrogate (will fail or ignore) - if (ch > 0 && (encoder == null || encoder.MustFlush)) - goto EncodeChar; - - // We're done - break; - } + Utf8.ToUtf16(bytes, chars, out int bytesRead, out int charsWritten, replaceInvalidSequences: true, isFinalBlock: decoder is null || decoder.MustFlush); - if (ch > 0) - { - // We have a high surrogate left over from a previous loop. - Debug.Assert(ch >= 0xD800 && ch <= 0xDBFF, - "[UTF8Encoding.GetBytes]expected high surrogate, not 0x" + ((int)ch).ToString("X4", CultureInfo.InvariantCulture)); - - // use separate helper variables for local contexts so that the jit optimizations - // won't get confused about the variable lifetimes - int cha = *pSrc; - - // In previous byte, we encountered a high surrogate, so we are expecting a low surrogate here. - // if (IsLowSurrogate(cha)) { - if (InRange(cha, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - ch = cha + (ch << 10) + - (0x10000 - - CharUnicodeInfo.LOW_SURROGATE_START - - (CharUnicodeInfo.HIGH_SURROGATE_START << 10)); - - pSrc++; - } - // else ch is still high surrogate and encoding will fail - - // attempt to encode the surrogate or partial surrogate - goto EncodeChar; - } - - // If we've used a fallback, then we have to check for it - if (fallbackBuffer != null) - { - ch = fallbackBuffer.InternalGetNextChar(); - if (ch > 0) goto ProcessChar; - } + // Slice off how much we consumed / wrote. - // read next char. The JIT optimization seems to be getting confused when - // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead - ch = *pSrc; - pSrc++; - - ProcessChar: - // if (IsHighSurrogate(ch)) { - if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.HIGH_SURROGATE_END)) - { - continue; - } - // either good char or partial surrogate - - EncodeChar: - // throw exception on partial surrogate if necessary - // if (IsLowSurrogate(ch) || IsHighSurrogate(ch)) - if (InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // Lone surrogates aren't allowed, we have to do fallback for them - // Have to make a fallback buffer if we don't have one - if (fallbackBuffer == null) - { - // wait on fallbacks if we can - // For fallback we may need a fallback buffer - if (baseEncoder == null) - fallbackBuffer = this.encoderFallback.CreateFallbackBuffer(); - else - fallbackBuffer = baseEncoder.FallbackBuffer; - - // Set our internal fallback interesting things. - fallbackBuffer.InternalInitialize(chars, pEnd, baseEncoder, true); - } - - // Do our fallback. Actually we already know its a mixed up surrogate, - // so the ref pSrc isn't gonna do anything. - pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered - fallbackBuffer.InternalFallback(unchecked((char)ch), ref pSrcForFallback); - pSrc = pSrcForFallback; - - // Ignore it if we don't throw - ch = 0; - continue; - } - - // Count bytes needed - int bytesNeeded = 1; - if (ch > 0x7F) - { - if (ch > 0x7FF) - { - if (ch > 0xFFFF) - { - bytesNeeded++; // 4 bytes (surrogate pair) - } - bytesNeeded++; // 3 bytes (800-FFFF) - } - bytesNeeded++; // 2 bytes (80-7FF) - } - - if (pTarget > pAllocatedBufferEnd - bytesNeeded) - { - // Left over surrogate from last time will cause pSrc == chars, so we'll throw - if (fallbackBuffer != null && fallbackBuffer.bFallingBack) - { - fallbackBuffer.MovePrevious(); // Didn't use this fallback char - if (ch > 0xFFFF) - fallbackBuffer.MovePrevious(); // Was surrogate, didn't use 2nd part either - } - else - { - pSrc--; // Didn't use this char - if (ch > 0xFFFF) - pSrc--; // Was surrogate, didn't use 2nd part either - } - Debug.Assert(pSrc >= chars || pTarget == bytes, - "[UTF8Encoding.GetBytes]Expected pSrc to be within buffer or to throw with insufficient room."); - ThrowBytesOverflow(encoder, pTarget == bytes); // Throw if we must - ch = 0; // Nothing left over (we backed up to start of pair if supplementary) - break; - } - - if (ch <= 0x7F) - { - *pTarget = (byte)ch; - } - else - { - // use separate helper variables for local contexts so that the jit optimizations - // won't get confused about the variable lifetimes - int chb; - if (ch <= 0x7FF) - { - // 2 byte encoding - chb = (byte)(unchecked((sbyte)0xC0) | (ch >> 6)); - } - else - { - if (ch <= 0xFFFF) - { - chb = (byte)(unchecked((sbyte)0xE0) | (ch >> 12)); - } - else - { - *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18)); - pTarget++; - - chb = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F; - } - *pTarget = (byte)chb; - pTarget++; - - chb = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F; - } - *pTarget = (byte)chb; - pTarget++; - - *pTarget = (byte)(unchecked((sbyte)0x80) | ch & 0x3F); - } - pTarget++; - - -#if FASTLOOP - // If still have fallback don't do fast loop - if (fallbackBuffer != null && (ch = fallbackBuffer.InternalGetNextChar()) != 0) - goto ProcessChar; - - int availableChars = PtrDiff(pEnd, pSrc); - int availableBytes = PtrDiff(pAllocatedBufferEnd, pTarget); - - // don't fall into the fast decoding loop if we don't have enough characters - // Note that if we don't have enough bytes, pStop will prevent us from entering the fast loop. - if (availableChars <= 13) - { - // we are hoping for 1 byte per char - if (availableBytes < availableChars) - { - // not enough output room. no pending bits at this point - ch = 0; - continue; - } - - // try to get over the remainder of the ascii characters fast though - char* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered - while (pSrc < pLocalEnd) - { - ch = *pSrc; - pSrc++; - - // Not ASCII, need more than 1 byte per char - if (ch > 0x7F) - goto ProcessChar; - - *pTarget = (byte)ch; - pTarget++; - } - // we are done, let ch be 0 to clear encoder - ch = 0; - break; - } - - // we need at least 1 byte per character, but Convert might allow us to convert - // only part of the input, so try as much as we can. Reduce charCount if necessary - if (availableBytes < availableChars) - { - availableChars = availableBytes; - } - - // FASTLOOP: - // - optimistic range checks - // - fallbacks to the slow loop for all special cases, exception throwing, etc. - - // To compute the upper bound, assume that all characters are ASCII characters at this point, - // the boundary will be decreased for every non-ASCII character we encounter - // Also, we need 5 chars reserve for the unrolled ansi decoding loop and for decoding of surrogates - // If there aren't enough bytes for the output, then pStop will be <= pSrc and will bypass the loop. - char* pStop = pSrc + availableChars - 5; - - while (pSrc < pStop) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) - { - goto LongCode; - } - *pTarget = (byte)ch; - pTarget++; - - // get pSrc aligned - if ((unchecked((int)pSrc) & 0x2) != 0) - { - ch = *pSrc; - pSrc++; - if (ch > 0x7F) - { - goto LongCode; - } - *pTarget = (byte)ch; - pTarget++; - } - - // Run 4 characters at a time! - while (pSrc < pStop) - { - ch = *(int*)pSrc; - int chc = *(int*)(pSrc + 2); - if (((ch | chc) & unchecked((int)0xFF80FF80)) != 0) - { - goto LongCodeWithMask; - } - - // Unfortunately, this is endianess sensitive - if (BitConverter.IsLittleEndian) - { - *pTarget = (byte)ch; - *(pTarget + 1) = (byte)(ch >> 16); - pSrc += 4; - *(pTarget + 2) = (byte)chc; - *(pTarget + 3) = (byte)(chc >> 16); - pTarget += 4; - } - else - { - *pTarget = (byte)(ch>>16); - *(pTarget+1) = (byte)ch; - pSrc += 4; - *(pTarget+2) = (byte)(chc>>16); - *(pTarget+3) = (byte)chc; - pTarget += 4; - } - } - continue; - - LongCodeWithMask: - if (BitConverter.IsLittleEndian) - { - ch = (char)ch; - } - else - { - // be careful about the sign extension - ch = (int)(((uint)ch) >> 16); - } - pSrc++; - - if (ch > 0x7F) - { - goto LongCode; - } - *pTarget = (byte)ch; - pTarget++; - continue; - - LongCode: - // use separate helper variables for slow and fast loop so that the jit optimizations - // won't get confused about the variable lifetimes - int chd; - if (ch <= 0x7FF) - { - // 2 byte encoding - chd = unchecked((sbyte)0xC0) | (ch >> 6); - } - else - { - // if (!IsLowSurrogate(ch) && !IsHighSurrogate(ch)) - if (!InRange(ch, CharUnicodeInfo.HIGH_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // 3 byte encoding - chd = unchecked((sbyte)0xE0) | (ch >> 12); - } - else - { - // 4 byte encoding - high surrogate + low surrogate - // if (!IsHighSurrogate(ch)) - if (ch > CharUnicodeInfo.HIGH_SURROGATE_END) - { - // low without high -> bad, try again in slow loop - pSrc -= 1; - break; - } - - chd = *pSrc; - pSrc++; - - // if (!IsLowSurrogate(chd)) { - if (!InRange(chd, CharUnicodeInfo.LOW_SURROGATE_START, CharUnicodeInfo.LOW_SURROGATE_END)) - { - // high not followed by low -> bad, try again in slow loop - pSrc -= 2; - break; - } - - ch = chd + (ch << 10) + - (0x10000 - - CharUnicodeInfo.LOW_SURROGATE_START - - (CharUnicodeInfo.HIGH_SURROGATE_START << 10)); - - *pTarget = (byte)(unchecked((sbyte)0xF0) | (ch >> 18)); - // pStop - this byte is compensated by the second surrogate character - // 2 input chars require 4 output bytes. 2 have been anticipated already - // and 2 more will be accounted for by the 2 pStop-- calls below. - pTarget++; - - chd = unchecked((sbyte)0x80) | (ch >> 12) & 0x3F; - } - *pTarget = (byte)chd; - pStop--; // 3 byte sequence for 1 char, so need pStop-- and the one below too. - pTarget++; - - chd = unchecked((sbyte)0x80) | (ch >> 6) & 0x3F; - } - *pTarget = (byte)chd; - pStop--; // 2 byte sequence for 1 char so need pStop--. - pTarget++; - - *pTarget = (byte)(unchecked((sbyte)0x80) | ch & 0x3F); - // pStop - this byte is already included - pTarget++; - } - - Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetBytes]pTarget <= pAllocatedBufferEnd"); + bytes = bytes.Slice(bytesRead); + chars = chars.Slice(charsWritten); + } -#endif // FASTLOOP + // If we couldn't go through our fast fallback mechanism, or if we still have leftover + // data because we couldn't consume everything in the loop above, we need to go down the + // slow fallback path. - // no pending char at this point - ch = 0; + if (bytes.IsEmpty) + { + return originalCharsLength - chars.Length; // total number of chars written } - - // Do we have to set the encoder bytes? - if (encoder != null) + else { - Debug.Assert(!encoder.MustFlush || ch == 0, - "[UTF8Encoding.GetBytes] Expected no mustflush or 0 leftover ch " + ch.ToString("X2", CultureInfo.InvariantCulture)); - - encoder.surrogateChar = ch; - encoder._charsUsed = (int)(pSrc - chars); + return base.GetCharsWithFallback(bytes, originalBytesLength, chars, originalCharsLength, decoder); } - - Debug.Assert(fallbackBuffer == null || fallbackBuffer.Remaining == 0 || - baseEncoder == null || !baseEncoder._throwOnOverflow, - "[UTF8Encoding.GetBytes]Expected empty fallback buffer if not converting"); - - return (int)(pTarget - bytes); } - - // These are bitmasks used to maintain the state in the decoder. They occupy the higher bits - // while the actual character is being built in the lower bits. They are shifted together - // with the actual bits of the character. - - // bits 30 & 31 are used for pending bits fixup - private const int FinalByte = 1 << 29; - private const int SupplimentarySeq = 1 << 28; - private const int ThreeByteSeq = 1 << 27; - - // Note: We throw exceptions on individually encoded surrogates and other non-shortest forms. - // If exceptions aren't turned on, then we drop all non-shortest &individual surrogates. + // Returns a string containing the decoded representation of a range of + // bytes in a byte array. // - // To simplify maintenance, the structure of GetCharCount and GetChars should be - // kept the same as much as possible - internal sealed override unsafe int GetCharCount(byte* bytes, int count, DecoderNLS baseDecoder) - { - Debug.Assert(count >= 0, "[UTF8Encoding.GetCharCount]count >=0"); - Debug.Assert(bytes != null, "[UTF8Encoding.GetCharCount]bytes!=null"); - - // Initialize stuff - byte* pSrc = bytes; - byte* pEnd = pSrc + count; + // All of our public Encodings that don't use EncodingNLS must have this (including EncodingNLS) + // So if you fix this, fix the others. Currently those include: + // EncodingNLS, UTF7Encoding, UTF8Encoding, UTF32Encoding, ASCIIEncoding, UnicodeEncoding + // parent method is safe - // Start by assuming we have as many as count, charCount always includes the adjustment - // for the character being decoded - int charCount = count; - int ch = 0; - DecoderFallbackBuffer fallback = null; + public override unsafe string GetString(byte[] bytes, int index, int count) + { + // Validate Parameters - if (baseDecoder != null) + if (bytes is null) { - UTF8Decoder decoder = (UTF8Decoder)baseDecoder; - ch = decoder.bits; - charCount -= (ch >> 30); // Adjust char count for # of expected bytes and expected output chars. - - // Shouldn't have anything in fallback buffer for GetCharCount - // (don't have to check _throwOnOverflow for count) - Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0, - "[UTF8Encoding.GetCharCount]Expected empty fallback buffer at start"); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.bytes, ExceptionResource.ArgumentNull_Array); } - for (;;) + if ((index | count) < 0) { - // SLOWLOOP: does all range checks, handles all special cases, but it is slow - - if (pSrc >= pEnd) - { - break; - } - - if (ch == 0) - { - // no pending bits - goto ReadChar; - } - - // read next byte. The JIT optimization seems to be getting confused when - // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead - int cha = *pSrc; - pSrc++; - - // we are expecting to see trailing bytes like 10vvvvvv - if ((cha & unchecked((sbyte)0xC0)) != 0x80) - { - // This can be a valid starting byte for another UTF8 byte sequence, so let's put - // the current byte back, and try to see if this is a valid byte for another UTF8 byte sequence - pSrc--; - charCount += (ch >> 30); - goto InvalidByteSequence; - } - - // fold in the new byte - ch = (ch << 6) | (cha & 0x3F); - - if ((ch & FinalByte) == 0) - { - Debug.Assert((ch & (SupplimentarySeq | ThreeByteSeq)) != 0, - "[UTF8Encoding.GetChars]Invariant volation"); - - if ((ch & SupplimentarySeq) != 0) - { - if ((ch & (FinalByte >> 6)) != 0) - { - // this is 3rd byte (of 4 byte supplementary) - nothing to do - continue; - } - - // 2nd byte, check for non-shortest form of supplementary char and the valid - // supplementary characters in range 0x010000 - 0x10FFFF at the same time - if (!InRange(ch & 0x1F0, 0x10, 0x100)) - { - goto InvalidByteSequence; - } - } - else - { - // Must be 2nd byte of a 3-byte sequence - // check for non-shortest form of 3 byte seq - if ((ch & (0x1F << 5)) == 0 || // non-shortest form - (ch & (0xF800 >> 6)) == (0xD800 >> 6)) // illegal individually encoded surrogate - { - goto InvalidByteSequence; - } - } - continue; - } - - // ready to punch - - // adjust for surrogates in non-shortest form - if ((ch & (SupplimentarySeq | 0x1F0000)) == SupplimentarySeq) - { - charCount--; - } - goto EncodeChar; - - InvalidByteSequence: - // this code fragment should be close to the goto referencing it - // Have to do fallback for invalid bytes - if (fallback == null) - { - if (baseDecoder == null) - fallback = this.decoderFallback.CreateFallbackBuffer(); - else - fallback = baseDecoder.FallbackBuffer; - fallback.InternalInitialize(bytes, null); - } - charCount += FallbackInvalidByteSequence(pSrc, ch, fallback); - - ch = 0; - continue; - - ReadChar: - ch = *pSrc; - pSrc++; - - ProcessChar: - if (ch > 0x7F) - { - // If its > 0x7F, its start of a new multi-byte sequence - - // Long sequence, so unreserve our char. - charCount--; - - // bit 6 has to be non-zero for start of multibyte chars. - if ((ch & 0x40) == 0) - { - // Unexpected trail byte - goto InvalidByteSequence; - } - - // start a new long code - if ((ch & 0x20) != 0) - { - if ((ch & 0x10) != 0) - { - // 4 byte encoding - supplimentary character (2 surrogates) - - ch &= 0x0F; - - // check that bit 4 is zero and the valid supplimentary character - // range 0x000000 - 0x10FFFF at the same time - if (ch > 0x04) - { - ch |= 0xf0; - goto InvalidByteSequence; - } - - // Add bit flags so that when we check new characters & rotate we'll be flagged correctly. - // Final byte flag, count fix if we don't make final byte & supplimentary sequence flag. - ch |= (FinalByte >> 3 * 6) | // Final byte is 3 more bytes from now - (1 << 30) | // If it dies on next byte we'll need an extra char - (3 << (30 - 2 * 6)) | // If it dies on last byte we'll need to subtract a char - (SupplimentarySeq) | (SupplimentarySeq >> 6) | - (SupplimentarySeq >> 2 * 6) | (SupplimentarySeq >> 3 * 6); - - // Our character count will be 2 characters for these 4 bytes, so subtract another char - charCount--; - } - else - { - // 3 byte encoding - // Add bit flags so that when we check new characters & rotate we'll be flagged correctly. - ch = (ch & 0x0F) | ((FinalByte >> 2 * 6) | (1 << 30) | - (ThreeByteSeq) | (ThreeByteSeq >> 6) | (ThreeByteSeq >> 2 * 6)); - - // We'll expect 1 character for these 3 bytes, so subtract another char. - charCount--; - } - } - else - { - // 2 byte encoding - - ch &= 0x1F; - - // check for non-shortest form - if (ch <= 1) - { - ch |= 0xc0; - goto InvalidByteSequence; - } - - // Add bit flags so we'll be flagged correctly - ch |= (FinalByte >> 6); - } - continue; - } - - EncodeChar: - -#if FASTLOOP - int availableBytes = PtrDiff(pEnd, pSrc); - - // don't fall into the fast decoding loop if we don't have enough bytes - if (availableBytes <= 13) - { - // try to get over the remainder of the ascii characters fast though - byte* pLocalEnd = pEnd; // hint to get pLocalEnd en-registered - while (pSrc < pLocalEnd) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) - goto ProcessChar; - } - // we are done - ch = 0; - break; - } - - // To compute the upper bound, assume that all characters are ASCII characters at this point, - // the boundary will be decreased for every non-ASCII character we encounter - // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences - byte* pStop = pSrc + availableBytes - 7; - - while (pSrc < pStop) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) - { - goto LongCode; - } - - // get pSrc 2-byte aligned - if ((unchecked((int)pSrc) & 0x1) != 0) - { - ch = *pSrc; - pSrc++; - if (ch > 0x7F) - { - goto LongCode; - } - } - - // get pSrc 4-byte aligned - if ((unchecked((int)pSrc) & 0x2) != 0) - { - ch = *(ushort*)pSrc; - if ((ch & 0x8080) != 0) - { - goto LongCodeWithMask16; - } - pSrc += 2; - } - - // Run 8 + 8 characters at a time! - while (pSrc < pStop) - { - ch = *(int*)pSrc; - int chb = *(int*)(pSrc + 4); - if (((ch | chb) & unchecked((int)0x80808080)) != 0) - { - goto LongCodeWithMask32; - } - pSrc += 8; - - // This is a really small loop - unroll it - if (pSrc >= pStop) - break; - - ch = *(int*)pSrc; - chb = *(int*)(pSrc + 4); - if (((ch | chb) & unchecked((int)0x80808080)) != 0) - { - goto LongCodeWithMask32; - } - pSrc += 8; - } - break; - - LongCodeWithMask32: - if (BitConverter.IsLittleEndian) - { - ch &= 0xFF; - } - else - { - // be careful about the sign extension - ch = (int)(((uint)ch) >> 16); - } - LongCodeWithMask16: - if (BitConverter.IsLittleEndian) - { - ch &= 0xFF; - } - else - { - ch = (int)(((uint)ch) >> 8); - } - - pSrc++; - if (ch <= 0x7F) - { - continue; - } - - LongCode: - int chc = *pSrc; - pSrc++; - - if ( - // bit 6 has to be zero - (ch & 0x40) == 0 || - // we are expecting to see trailing bytes like 10vvvvvv - (chc & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - - chc &= 0x3F; - - // start a new long code - if ((ch & 0x20) != 0) - { - // fold the first two bytes together - chc |= (ch & 0x0F) << 6; - - if ((ch & 0x10) != 0) - { - // 4 byte encoding - surrogate - ch = *pSrc; - if ( - // check that bit 4 is zero, the non-shortest form of surrogate - // and the valid surrogate range 0x000000 - 0x10FFFF at the same time - !InRange(chc >> 4, 0x01, 0x10) || - // we are expecting to see trailing bytes like 10vvvvvv - (ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - - chc = (chc << 6) | (ch & 0x3F); - - ch = *(pSrc + 1); - // we are expecting to see trailing bytes like 10vvvvvv - if ((ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - pSrc += 2; - - // extra byte - charCount--; - } - else - { - // 3 byte encoding - ch = *pSrc; - if ( - // check for non-shortest form of 3 byte seq - (chc & (0x1F << 5)) == 0 || - // Can't have surrogates here. - (chc & (0xF800 >> 6)) == (0xD800 >> 6) || - // we are expecting to see trailing bytes like 10vvvvvv - (ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - pSrc++; - - // extra byte - charCount--; - } - } - else - { - // 2 byte encoding - - // check for non-shortest form - if ((ch & 0x1E) == 0) - { - goto BadLongCode; - } - } - - // extra byte - charCount--; - } -#endif // FASTLOOP - - // no pending bits at this point - ch = 0; - continue; - - BadLongCode: - pSrc -= 2; - ch = 0; - continue; + ThrowHelper.ThrowArgumentOutOfRangeException( + argument: (index < 0) ? ExceptionArgument.index : ExceptionArgument.count, + resource: ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); } - // May have a problem if we have to flush - if (ch != 0) + if (bytes.Length - index < count) { - // We were already adjusting for these, so need to un-adjust - charCount += (ch >> 30); - if (baseDecoder == null || baseDecoder.MustFlush) - { - // Have to do fallback for invalid bytes - if (fallback == null) - { - if (baseDecoder == null) - fallback = this.decoderFallback.CreateFallbackBuffer(); - else - fallback = baseDecoder.FallbackBuffer; - fallback.InternalInitialize(bytes, null); - } - charCount += FallbackInvalidByteSequence(pSrc, ch, fallback); - } + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.bytes, ExceptionResource.ArgumentOutOfRange_IndexCountBuffer); } - // Shouldn't have anything in fallback buffer for GetCharCount - // (don't have to check _throwOnOverflow for count) - Debug.Assert(fallback == null || fallback.Remaining == 0, - "[UTF8Encoding.GetCharCount]Expected empty fallback buffer at end"); + // Avoid problems with empty input buffer + if (count == 0) + return string.Empty; - return charCount; + fixed (byte* pBytes = bytes) + { + return string.CreateStringFromEncoding(pBytes + index, count, this); + } } - // WARNING: If we throw an error, then System.Resources.ResourceReader calls this method. - // So if we're really broken, then that could also throw an error... recursively. - // So try to make sure GetChars can at least process all uses by - // System.Resources.ResourceReader! // - // Note: We throw exceptions on individually encoded surrogates and other non-shortest forms. - // If exceptions aren't turned on, then we drop all non-shortest &individual surrogates. + // End of standard methods copied from EncodingNLS.cs // - // To simplify maintenance, the structure of GetCharCount and GetChars should be - // kept the same as much as possible - internal sealed override unsafe int GetChars( - byte* bytes, int byteCount, char* chars, int charCount, DecoderNLS baseDecoder) - { - Debug.Assert(chars != null, "[UTF8Encoding.GetChars]chars!=null"); - Debug.Assert(byteCount >= 0, "[UTF8Encoding.GetChars]count >=0"); - Debug.Assert(charCount >= 0, "[UTF8Encoding.GetChars]charCount >=0"); - Debug.Assert(bytes != null, "[UTF8Encoding.GetChars]bytes!=null"); - - byte* pSrc = bytes; - char* pTarget = chars; - - byte* pEnd = pSrc + byteCount; - char* pAllocatedBufferEnd = pTarget + charCount; - - int ch = 0; - - DecoderFallbackBuffer fallback = null; - byte* pSrcForFallback; - char* pTargetForFallback; - if (baseDecoder != null) - { - UTF8Decoder decoder = (UTF8Decoder)baseDecoder; - ch = decoder.bits; - - // Shouldn't have anything in fallback buffer for GetChars - // (don't have to check _throwOnOverflow for chars, we always use all or none so always should be empty) - Debug.Assert(!decoder.InternalHasFallbackBuffer || decoder.FallbackBuffer.Remaining == 0, - "[UTF8Encoding.GetChars]Expected empty fallback buffer at start"); - } - for (;;) - { - // SLOWLOOP: does all range checks, handles all special cases, but it is slow - - if (pSrc >= pEnd) - { - break; - } - - if (ch == 0) - { - // no pending bits - goto ReadChar; - } - - // read next byte. The JIT optimization seems to be getting confused when - // compiling "ch = *pSrc++;", so rather use "ch = *pSrc; pSrc++;" instead - int cha = *pSrc; - pSrc++; - - // we are expecting to see trailing bytes like 10vvvvvv - if ((cha & unchecked((sbyte)0xC0)) != 0x80) - { - // This can be a valid starting byte for another UTF8 byte sequence, so let's put - // the current byte back, and try to see if this is a valid byte for another UTF8 byte sequence - pSrc--; - goto InvalidByteSequence; - } - - // fold in the new byte - ch = (ch << 6) | (cha & 0x3F); - - if ((ch & FinalByte) == 0) - { - // Not at last byte yet - Debug.Assert((ch & (SupplimentarySeq | ThreeByteSeq)) != 0, - "[UTF8Encoding.GetChars]Invariant volation"); - - if ((ch & SupplimentarySeq) != 0) - { - // Its a 4-byte supplimentary sequence - if ((ch & (FinalByte >> 6)) != 0) - { - // this is 3rd byte of 4 byte sequence - nothing to do - continue; - } - - // 2nd byte of 4 bytes - // check for non-shortest form of surrogate and the valid surrogate - // range 0x000000 - 0x10FFFF at the same time - if (!InRange(ch & 0x1F0, 0x10, 0x100)) - { - goto InvalidByteSequence; - } - } - else - { - // Must be 2nd byte of a 3-byte sequence - // check for non-shortest form of 3 byte seq - if ((ch & (0x1F << 5)) == 0 || // non-shortest form - (ch & (0xF800 >> 6)) == (0xD800 >> 6)) // illegal individually encoded surrogate - { - goto InvalidByteSequence; - } - } - continue; - } - - // ready to punch - - // surrogate in shortest form? - // Might be possible to get rid of this? Already did non-shortest check for 4-byte sequence when reading 2nd byte? - if ((ch & (SupplimentarySeq | 0x1F0000)) > SupplimentarySeq) - { - // let the range check for the second char throw the exception - if (pTarget < pAllocatedBufferEnd) - { - *pTarget = (char)(((ch >> 10) & 0x7FF) + - unchecked((short)((CharUnicodeInfo.HIGH_SURROGATE_START - (0x10000 >> 10))))); - pTarget++; - - ch = (ch & 0x3FF) + - unchecked((int)(CharUnicodeInfo.LOW_SURROGATE_START)); - } - } - - goto EncodeChar; - - InvalidByteSequence: - // this code fragment should be close to the gotos referencing it - // Have to do fallback for invalid bytes - if (fallback == null) - { - if (baseDecoder == null) - fallback = this.decoderFallback.CreateFallbackBuffer(); - else - fallback = baseDecoder.FallbackBuffer; - fallback.InternalInitialize(bytes, pAllocatedBufferEnd); - } - // That'll back us up the appropriate # of bytes if we didn't get anywhere - pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered - pTargetForFallback = pTarget; // Avoid passing pTarget by reference to allow it to be en-registered - bool fallbackResult = FallbackInvalidByteSequence(ref pSrcForFallback, ch, fallback, ref pTargetForFallback); - pSrc = pSrcForFallback; - pTarget = pTargetForFallback; - - if (!fallbackResult) - { - // Ran out of buffer space - // Need to throw an exception? - Debug.Assert(pSrc >= bytes || pTarget == chars, - "[UTF8Encoding.GetChars]Expected to throw or remain in byte buffer after fallback"); - fallback.InternalReset(); - ThrowCharsOverflow(baseDecoder, pTarget == chars); - ch = 0; - break; - } - Debug.Assert(pSrc >= bytes, - "[UTF8Encoding.GetChars]Expected invalid byte sequence to have remained within the byte array"); - ch = 0; - continue; - - ReadChar: - ch = *pSrc; - pSrc++; - - ProcessChar: - if (ch > 0x7F) - { - // If its > 0x7F, its start of a new multi-byte sequence - - // bit 6 has to be non-zero - if ((ch & 0x40) == 0) - { - goto InvalidByteSequence; - } - - // start a new long code - if ((ch & 0x20) != 0) - { - if ((ch & 0x10) != 0) - { - // 4 byte encoding - supplimentary character (2 surrogates) - - ch &= 0x0F; - - // check that bit 4 is zero and the valid supplimentary character - // range 0x000000 - 0x10FFFF at the same time - if (ch > 0x04) - { - ch |= 0xf0; - goto InvalidByteSequence; - } - - ch |= (FinalByte >> 3 * 6) | (1 << 30) | (3 << (30 - 2 * 6)) | - (SupplimentarySeq) | (SupplimentarySeq >> 6) | - (SupplimentarySeq >> 2 * 6) | (SupplimentarySeq >> 3 * 6); - } - else - { - // 3 byte encoding - ch = (ch & 0x0F) | ((FinalByte >> 2 * 6) | (1 << 30) | - (ThreeByteSeq) | (ThreeByteSeq >> 6) | (ThreeByteSeq >> 2 * 6)); - } - } - else - { - // 2 byte encoding - - ch &= 0x1F; - - // check for non-shortest form - if (ch <= 1) - { - ch |= 0xc0; - goto InvalidByteSequence; - } - - ch |= (FinalByte >> 6); - } - continue; - } - - EncodeChar: - // write the pending character - if (pTarget >= pAllocatedBufferEnd) - { - // Fix chars so we make sure to throw if we didn't output anything - ch &= 0x1fffff; - if (ch > 0x7f) - { - if (ch > 0x7ff) - { - if (ch >= CharUnicodeInfo.LOW_SURROGATE_START && - ch <= CharUnicodeInfo.LOW_SURROGATE_END) - { - pSrc--; // It was 4 bytes - pTarget--; // 1 was stored already, but we can't remember 1/2, so back up - } - else if (ch > 0xffff) - { - pSrc--; // It was 4 bytes, nothing was stored - } - pSrc--; // It was at least 3 bytes - } - pSrc--; // It was at least 2 bytes - } - pSrc--; - - // Throw that we don't have enough room (pSrc could be < chars if we had started to process - // a 4 byte sequence already) - Debug.Assert(pSrc >= bytes || pTarget == chars, - "[UTF8Encoding.GetChars]Expected pSrc to be within input buffer or throw due to no output]"); - ThrowCharsOverflow(baseDecoder, pTarget == chars); - - // Don't store ch in decoder, we already backed up to its start - ch = 0; - - // Didn't throw, just use this buffer size. - break; - } - *pTarget = (char)ch; - pTarget++; - -#if FASTLOOP - int availableChars = PtrDiff(pAllocatedBufferEnd, pTarget); - int availableBytes = PtrDiff(pEnd, pSrc); - - // don't fall into the fast decoding loop if we don't have enough bytes - // Test for availableChars is done because pStop would be <= pTarget. - if (availableBytes <= 13) - { - // we may need as many as 1 character per byte - if (availableChars < availableBytes) - { - // not enough output room. no pending bits at this point - ch = 0; - continue; - } - - // try to get over the remainder of the ascii characters fast though - byte* pLocalEnd = pEnd; // hint to get pLocalEnd enregistered - while (pSrc < pLocalEnd) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) - goto ProcessChar; - - *pTarget = (char)ch; - pTarget++; - } - // we are done - ch = 0; - break; - } - - // we may need as many as 1 character per byte, so reduce the byte count if necessary. - // If availableChars is too small, pStop will be before pTarget and we won't do fast loop. - if (availableChars < availableBytes) - { - availableBytes = availableChars; - } - - // To compute the upper bound, assume that all characters are ASCII characters at this point, - // the boundary will be decreased for every non-ASCII character we encounter - // Also, we need 7 chars reserve for the unrolled ansi decoding loop and for decoding of multibyte sequences - char* pStop = pTarget + availableBytes - 7; - - while (pTarget < pStop) - { - ch = *pSrc; - pSrc++; - - if (ch > 0x7F) - { - goto LongCode; - } - *pTarget = (char)ch; - pTarget++; - - // get pSrc to be 2-byte aligned - if ((unchecked((int)pSrc) & 0x1) != 0) - { - ch = *pSrc; - pSrc++; - if (ch > 0x7F) - { - goto LongCode; - } - *pTarget = (char)ch; - pTarget++; - } - - // get pSrc to be 4-byte aligned - if ((unchecked((int)pSrc) & 0x2) != 0) - { - ch = *(ushort*)pSrc; - if ((ch & 0x8080) != 0) - { - goto LongCodeWithMask16; - } - - // Unfortunately, this is endianess sensitive - if (BitConverter.IsLittleEndian) - { - *pTarget = (char)(ch & 0x7F); - pSrc += 2; - *(pTarget + 1) = (char)((ch >> 8) & 0x7F); - pTarget += 2; - } - else - { - *pTarget = (char)((ch >> 8) & 0x7F); - pSrc += 2; - *(pTarget+1) = (char)(ch & 0x7F); - pTarget += 2; - } - } - - // Run 8 characters at a time! - while (pTarget < pStop) - { - ch = *(int*)pSrc; - int chb = *(int*)(pSrc + 4); - if (((ch | chb) & unchecked((int)0x80808080)) != 0) - { - goto LongCodeWithMask32; - } - - // Unfortunately, this is endianess sensitive - if (BitConverter.IsLittleEndian) - { - *pTarget = (char)(ch & 0x7F); - *(pTarget + 1) = (char)((ch >> 8) & 0x7F); - *(pTarget + 2) = (char)((ch >> 16) & 0x7F); - *(pTarget + 3) = (char)((ch >> 24) & 0x7F); - pSrc += 8; - *(pTarget + 4) = (char)(chb & 0x7F); - *(pTarget + 5) = (char)((chb >> 8) & 0x7F); - *(pTarget + 6) = (char)((chb >> 16) & 0x7F); - *(pTarget + 7) = (char)((chb >> 24) & 0x7F); - pTarget += 8; - } - else - { - *pTarget = (char)((ch >> 24) & 0x7F); - *(pTarget+1) = (char)((ch >> 16) & 0x7F); - *(pTarget+2) = (char)((ch >> 8) & 0x7F); - *(pTarget+3) = (char)(ch & 0x7F); - pSrc += 8; - *(pTarget+4) = (char)((chb >> 24) & 0x7F); - *(pTarget+5) = (char)((chb >> 16) & 0x7F); - *(pTarget+6) = (char)((chb >> 8) & 0x7F); - *(pTarget+7) = (char)(chb & 0x7F); - pTarget += 8; - } - } - break; - - LongCodeWithMask32: - if (BitConverter.IsLittleEndian) - { - ch &= 0xFF; - } - else - { - // be careful about the sign extension - ch = (int)(((uint)ch) >> 16); - } - LongCodeWithMask16: - if (BitConverter.IsLittleEndian) - { - ch &= 0xFF; - } - else - { - ch = (int)(((uint)ch) >> 8); - } - pSrc++; - if (ch <= 0x7F) - { - *pTarget = (char)ch; - pTarget++; - continue; - } - - LongCode: - int chc = *pSrc; - pSrc++; - - if ( - // bit 6 has to be zero - (ch & 0x40) == 0 || - // we are expecting to see trailing bytes like 10vvvvvv - (chc & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - - chc &= 0x3F; - - // start a new long code - if ((ch & 0x20) != 0) - { - // fold the first two bytes together - chc |= (ch & 0x0F) << 6; - - if ((ch & 0x10) != 0) - { - // 4 byte encoding - surrogate - ch = *pSrc; - if ( - // check that bit 4 is zero, the non-shortest form of surrogate - // and the valid surrogate range 0x000000 - 0x10FFFF at the same time - !InRange(chc >> 4, 0x01, 0x10) || - // we are expecting to see trailing bytes like 10vvvvvv - (ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - - chc = (chc << 6) | (ch & 0x3F); - - ch = *(pSrc + 1); - // we are expecting to see trailing bytes like 10vvvvvv - if ((ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - pSrc += 2; - - ch = (chc << 6) | (ch & 0x3F); - - *pTarget = (char)(((ch >> 10) & 0x7FF) + - unchecked((short)(CharUnicodeInfo.HIGH_SURROGATE_START - (0x10000 >> 10)))); - pTarget++; - - ch = (ch & 0x3FF) + - unchecked((short)(CharUnicodeInfo.LOW_SURROGATE_START)); - - // extra byte, we're already planning 2 chars for 2 of these bytes, - // but the big loop is testing the target against pStop, so we need - // to subtract 2 more or we risk overrunning the input. Subtract - // one here and one below. - pStop--; - } - else - { - // 3 byte encoding - ch = *pSrc; - if ( - // check for non-shortest form of 3 byte seq - (chc & (0x1F << 5)) == 0 || - // Can't have surrogates here. - (chc & (0xF800 >> 6)) == (0xD800 >> 6) || - // we are expecting to see trailing bytes like 10vvvvvv - (ch & unchecked((sbyte)0xC0)) != 0x80) - { - goto BadLongCode; - } - pSrc++; - - ch = (chc << 6) | (ch & 0x3F); - - // extra byte, we're only expecting 1 char for each of these 3 bytes, - // but the loop is testing the target (not source) against pStop, so - // we need to subtract 2 more or we risk overrunning the input. - // Subtract 1 here and one more below - pStop--; - } - } - else - { - // 2 byte encoding - - ch &= 0x1F; - - // check for non-shortest form - if (ch <= 1) - { - goto BadLongCode; - } - ch = (ch << 6) | chc; - } - - *pTarget = (char)ch; - pTarget++; - - // extra byte, we're only expecting 1 char for each of these 2 bytes, - // but the loop is testing the target (not source) against pStop. - // subtract an extra count from pStop so that we don't overrun the input. - pStop--; - } -#endif // FASTLOOP + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe int GetCharCountCommon(byte* pBytes, int byteCount) + { + // Common helper method for all non-DecoderNLS entry points to GetCharCount. + // A modification of this method should be copied in to each of the supported encodings: ASCII, UTF8, UTF16, UTF32. - Debug.Assert(pTarget <= pAllocatedBufferEnd, "[UTF8Encoding.GetChars]pTarget <= pAllocatedBufferEnd"); + Debug.Assert(byteCount >= 0, "Caller shouldn't specify negative length buffer."); + Debug.Assert(pBytes != null || byteCount == 0, "Input pointer shouldn't be null if non-zero length specified."); - // no pending bits at this point - ch = 0; - continue; + // First call into the fast path. + // Don't bother providing a fallback mechanism; our fast path doesn't use it. - BadLongCode: - pSrc -= 2; - ch = 0; - continue; - } + int totalCharCount = GetCharCountFast(pBytes, byteCount, fallback: null, out int bytesConsumed); - if (ch != 0 && (baseDecoder == null || baseDecoder.MustFlush)) + if (bytesConsumed != byteCount) { - // Have to do fallback for invalid bytes - if (fallback == null) - { - if (baseDecoder == null) - fallback = this.decoderFallback.CreateFallbackBuffer(); - else - fallback = baseDecoder.FallbackBuffer; - fallback.InternalInitialize(bytes, pAllocatedBufferEnd); - } - - // That'll back us up the appropriate # of bytes if we didn't get anywhere - pSrcForFallback = pSrc; // Avoid passing pSrc by reference to allow it to be en-registered - pTargetForFallback = pTarget; // Avoid passing pTarget by reference to allow it to be en-registered - bool fallbackResult = FallbackInvalidByteSequence(ref pSrcForFallback, ch, fallback, ref pTargetForFallback); - pSrc = pSrcForFallback; - pTarget = pTargetForFallback; + // If there's still data remaining in the source buffer, go down the fallback path. + // We need to check for integer overflow since the fallback could change the required + // output count in unexpected ways. - if (!fallbackResult) + totalCharCount += GetCharCountWithFallback(pBytes, byteCount, bytesConsumed); + if (totalCharCount < 0) { - Debug.Assert(pSrc >= bytes || pTarget == chars, - "[UTF8Encoding.GetChars]Expected to throw or remain in byte buffer while flushing"); - - // Ran out of buffer space - // Need to throw an exception? - fallback.InternalReset(); - ThrowCharsOverflow(baseDecoder, pTarget == chars); + ThrowConversionOverflow(); } - Debug.Assert(pSrc >= bytes, - "[UTF8Encoding.GetChars]Expected flushing invalid byte sequence to have remained within the byte array"); - ch = 0; } - if (baseDecoder != null) - { - UTF8Decoder decoder = (UTF8Decoder)baseDecoder; + return totalCharCount; + } - // If we're storing flush data we expect all bits to be used or else - // we're stuck in the middle of a conversion - Debug.Assert(!baseDecoder.MustFlush || ch == 0 || !baseDecoder._throwOnOverflow, - "[UTF8Encoding.GetChars]Expected no must flush or no left over bits or no throw on overflow."); + [MethodImpl(MethodImplOptions.AggressiveInlining)] // called directly by GetCharCountCommon + private protected sealed override unsafe int GetCharCountFast(byte* pBytes, int bytesLength, DecoderFallback fallback, out int bytesConsumed) + { + // The number of UTF-16 code units will never exceed the number of UTF-8 code units, + // so the addition at the end of this method will not overflow. - // Remember our leftover bits. - decoder.bits = ch; + byte* ptrToFirstInvalidByte = Utf8Utility.GetPointerToFirstInvalidByte(pBytes, bytesLength, out int utf16CodeUnitCountAdjustment, out _); - baseDecoder._bytesUsed = (int)(pSrc - bytes); - } + int tempBytesConsumed = (int)(ptrToFirstInvalidByte - pBytes); + bytesConsumed = tempBytesConsumed; - // Shouldn't have anything in fallback buffer for GetChars - // (don't have to check _throwOnOverflow for chars) - Debug.Assert(fallback == null || fallback.Remaining == 0, - "[UTF8Encoding.GetChars]Expected empty fallback buffer at end"); - - return PtrDiff(pTarget, chars); + return tempBytesConsumed + utf16CodeUnitCountAdjustment; } - // During GetChars we had an invalid byte sequence - // pSrc is backed up to the start of the bad sequence if we didn't have room to - // fall it back. Otherwise pSrc remains where it is. - private unsafe bool FallbackInvalidByteSequence( - ref byte* pSrc, int ch, DecoderFallbackBuffer fallback, ref char* pTarget) + public override Decoder GetDecoder() { - // Get our byte[] - byte* pStart = pSrc; - byte[] bytesUnknown = GetBytesUnknown(ref pStart, ch); - - // Do the actual fallback - if (!fallback.InternalFallback(bytesUnknown, pSrc, ref pTarget)) - { - // Oops, it failed, back up to pStart - pSrc = pStart; - return false; - } - - // It worked - return true; + return new DecoderNLS(this); } - // During GetCharCount we had an invalid byte sequence - // pSrc is used to find the index that points to the invalid bytes, - // however the byte[] contains the fallback bytes (in case the index is -1) - private unsafe int FallbackInvalidByteSequence( - byte* pSrc, int ch, DecoderFallbackBuffer fallback) + + public override Encoder GetEncoder() { - // Calling GetBytesUnknown can adjust the pSrc pointer but we need to pass the pointer before the adjustment - // to fallback.InternalFallback. The input pSrc to fallback.InternalFallback will only be used to calculate the - // index inside bytesUnknown and if we pass the adjusted pointer we can end up with negative index values. - // We store the original pSrc in pOriginalSrc and then pass pOriginalSrc to fallback.InternalFallback. - byte* pOriginalSrc = pSrc; - - // Get our byte[] - byte[] bytesUnknown = GetBytesUnknown(ref pSrc, ch); - - // Do the actual fallback - int count = fallback.InternalFallback(bytesUnknown, pOriginalSrc); - - // # of fallback chars expected. - // Note that we only get here for "long" sequences, and have already unreserved - // the count that we prereserved for the input bytes - return count; + return new EncoderNLS(this); } - // Note that some of these bytes may have come from a previous fallback, so we cannot - // just decrement the pointer and use the values we read. In those cases we have - // to regenerate the original values. - private unsafe byte[] GetBytesUnknown(ref byte* pSrc, int ch) - { - // Get our byte[] - byte[] bytesUnknown = null; + // + // Beginning of methods used by shared fallback logic. + // - // See if it was a plain char - // (have to check >= 0 because we have all sorts of wierd bit flags) - if (ch < 0x100 && ch >= 0) - { - pSrc--; - bytesUnknown = new byte[] { unchecked((byte)ch) }; - } - // See if its an unfinished 2 byte sequence - else if ((ch & (SupplimentarySeq | ThreeByteSeq)) == 0) - { - pSrc--; - bytesUnknown = new byte[] { unchecked((byte)((ch & 0x1F) | 0xc0)) }; - } - // So now we're either 2nd byte of 3 or 4 byte sequence or - // we hit a non-trail byte or we ran out of space for 3rd byte of 4 byte sequence - // 1st check if its a 4 byte sequence - else if ((ch & SupplimentarySeq) != 0) - { - // 3rd byte of 4 byte sequence? - if ((ch & (FinalByte >> 6)) != 0) - { - // 3rd byte of 4 byte sequence - pSrc -= 3; - bytesUnknown = new byte[] { - unchecked((byte)(((ch >> 12) & 0x07) | 0xF0)), - unchecked((byte)(((ch >> 6) & 0x3F) | 0x80)), - unchecked((byte)(((ch) & 0x3F) | 0x80)) }; - } - else if ((ch & (FinalByte >> 12)) != 0) - { - // 2nd byte of a 4 byte sequence - pSrc -= 2; - bytesUnknown = new byte[] { - unchecked((byte)(((ch >> 6) & 0x07) | 0xF0)), - unchecked((byte)(((ch) & 0x3F) | 0x80)) }; - } - else - { - // 4th byte of a 4 byte sequence - pSrc--; - bytesUnknown = new byte[] { unchecked((byte)(((ch) & 0x07) | 0xF0)) }; - } - } - else - { - // 2nd byte of 3 byte sequence? - if ((ch & (FinalByte >> 6)) != 0) - { - // So its 2nd byte of a 3 byte sequence - pSrc -= 2; - bytesUnknown = new byte[] { - unchecked((byte)(((ch >> 6) & 0x0F) | 0xE0)), unchecked ((byte)(((ch) & 0x3F) | 0x80)) }; - } - else - { - // 1st byte of a 3 byte sequence - pSrc--; - bytesUnknown = new byte[] { unchecked((byte)(((ch) & 0x0F) | 0xE0)) }; - } - } + internal sealed override bool TryGetByteCount(Rune value, out int byteCount) + { + // All well-formed Rune instances can be converted to 1..4 UTF-8 code units. - return bytesUnknown; + byteCount = value.Utf8SequenceLength; + return true; } - - public override Decoder GetDecoder() + internal sealed override OperationStatus EncodeRune(Rune value, Span bytes, out int bytesWritten) { - return new UTF8Decoder(this); - } + // All well-formed Rune instances can be encoded as 1..4 UTF-8 code units. + // If there's an error, it's because the destination was too small. + return value.TryEncodeToUtf8(bytes, out bytesWritten) ? OperationStatus.Done : OperationStatus.DestinationTooSmall; + } - public override Encoder GetEncoder() + internal sealed override OperationStatus DecodeFirstRune(ReadOnlySpan bytes, out Rune value, out int bytesConsumed) { - return new UTF8Encoder(this); + return Rune.DecodeFromUtf8(bytes, out value, out bytesConsumed); } + // + // End of methods used by shared fallback logic. + // public override int GetMaxByteCount(int charCount) { @@ -2571,62 +854,5 @@ public override int GetHashCode() return this.EncoderFallback.GetHashCode() + this.DecoderFallback.GetHashCode() + UTF8_CODEPAGE + (_emitUTF8Identifier ? 1 : 0); } - - private sealed class UTF8Encoder : EncoderNLS - { - // We must save a high surrogate value until the next call, looking - // for a low surrogate value. - internal int surrogateChar; - - public UTF8Encoder(UTF8Encoding encoding) : base(encoding) - { - // base calls reset - } - - public override void Reset() - - { - this.surrogateChar = 0; - if (_fallbackBuffer != null) - _fallbackBuffer.Reset(); - } - - // Anything left in our encoder? - internal override bool HasState - { - get - { - return (this.surrogateChar != 0); - } - } - } - - private sealed class UTF8Decoder : DecoderNLS - { - // We'll need to remember the previous information. See the comments around definition - // of FinalByte for details. - internal int bits; - - public UTF8Decoder(UTF8Encoding encoding) : base(encoding) - { - // base calls reset - } - - public override void Reset() - { - this.bits = 0; - if (_fallbackBuffer != null) - _fallbackBuffer.Reset(); - } - - // Anything left in our decoder? - internal override bool HasState - { - get - { - return (this.bits != 0); - } - } - } } } diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.Validation.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.Validation.cs new file mode 100644 index 000000000000..bdf797217667 --- /dev/null +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.Validation.cs @@ -0,0 +1,428 @@ +// 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.Diagnostics; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +using System.Numerics; +using Internal.Runtime.CompilerServices; + +#if BIT64 +using nint = System.Int64; +using nuint = System.UInt64; +#else // BIT64 +using nint = System.Int32; +using nuint = System.UInt32; +#endif // BIT64 + +namespace System.Text.Unicode +{ + internal static unsafe partial class Utf16Utility + { +#if DEBUG + static Utf16Utility() + { + Debug.Assert(sizeof(nint) == IntPtr.Size && nint.MinValue < 0, "nint is defined incorrectly."); + Debug.Assert(sizeof(nuint) == IntPtr.Size && nuint.MinValue == 0, "nuint is defined incorrectly."); + } +#endif // DEBUG + + // Returns &inputBuffer[inputLength] if the input buffer is valid. + /// + /// Given an input buffer of char length , + /// returns a pointer to where the first invalid data appears in . + /// + /// + /// Returns a pointer to the end of if the buffer is well-formed. + /// + public static char* GetPointerToFirstInvalidChar(char* pInputBuffer, int inputLength, out long utf8CodeUnitCountAdjustment, out int scalarCountAdjustment) + { + Debug.Assert(inputLength >= 0, "Input length must not be negative."); + Debug.Assert(pInputBuffer != null || inputLength == 0, "Input length must be zero if input buffer pointer is null."); + + // First, we'll handle the common case of all-ASCII. If this is able to + // consume the entire buffer, we'll skip the remainder of this method's logic. + + int numAsciiCharsConsumedJustNow = (int)ASCIIUtility.GetIndexOfFirstNonAsciiChar(pInputBuffer, (uint)inputLength); + Debug.Assert(0 <= numAsciiCharsConsumedJustNow && numAsciiCharsConsumedJustNow <= inputLength); + + pInputBuffer += (uint)numAsciiCharsConsumedJustNow; + inputLength -= numAsciiCharsConsumedJustNow; + + if (inputLength == 0) + { + utf8CodeUnitCountAdjustment = 0; + scalarCountAdjustment = 0; + return pInputBuffer; + } + + // If we got here, it means we saw some non-ASCII data, so within our + // vectorized code paths below we'll handle all non-surrogate UTF-16 + // code points branchlessly. We'll only branch if we see surrogates. + // + // We still optimistically assume the data is mostly ASCII. This means that the + // number of UTF-8 code units and the number of scalars almost matches the number + // of UTF-16 code units. As we go through the input and find non-ASCII + // characters, we'll keep track of these "adjustment" fixups. To get the + // total number of UTF-8 code units required to encode the input data, add + // the UTF-8 code unit count adjustment to the number of UTF-16 code units + // seen. To get the total number of scalars present in the input data, + // add the scalar count adjustment to the number of UTF-16 code units seen. + + long tempUtf8CodeUnitCountAdjustment = 0; + int tempScalarCountAdjustment = 0; + + if (Sse2.IsSupported) + { + if (inputLength >= Vector128.Count) + { + Vector128 vector0080 = Vector128.Create((ushort)0x80); + Vector128 vectorA800 = Vector128.Create((ushort)0xA800); + Vector128 vector8800 = Vector128.Create(unchecked((short)0x8800)); + Vector128 vectorZero = Vector128.Zero; + + do + { + Vector128 utf16Data = Sse2.LoadVector128((ushort*)pInputBuffer); // unaligned + uint mask; + + // The 'charIsNonAscii' vector we're about to build will have the 0x8000 or the 0x0080 + // bit set (but not both!) only if the corresponding input char is non-ASCII. Which of + // the two bits is set doesn't matter, as will be explained in the diagram a few lines + // below. + + Vector128 charIsNonAscii; + if (Sse41.IsSupported) + { + // sets 0x0080 bit if corresponding char element is >= 0x0080 + charIsNonAscii = Sse41.Min(utf16Data, vector0080); + } + else + { + // sets 0x8000 bit if corresponding char element is >= 0x0080 + charIsNonAscii = Sse2.AndNot(vector0080, Sse2.Subtract(vectorZero, Sse2.ShiftRightLogical(utf16Data, 7))); + } + +#if DEBUG + // Quick check to ensure we didn't accidentally set both 0x8080 bits in any element. + uint debugMask = (uint)Sse2.MoveMask(charIsNonAscii.AsByte()); + Debug.Assert((debugMask & (debugMask << 1)) == 0, "Two set bits shouldn't occur adjacent to each other in this mask."); +#endif // DEBUG + + // sets 0x8080 bits if corresponding char element is >= 0x0800 + Vector128 charIsThreeByteUtf8Encoded = Sse2.Subtract(vectorZero, Sse2.ShiftRightLogical(utf16Data, 11)); + + mask = (uint)Sse2.MoveMask(Sse2.Or(charIsNonAscii, charIsThreeByteUtf8Encoded).AsByte()); + + // Each odd bit of mask will be 1 only if the char was >= 0x0080, + // and each even bit of mask will be 1 only if the char was >= 0x0800. + // + // Example for UTF-16 input "[ 0123 ] [ 1234 ] ...": + // + // ,-- set if char[1] is non-ASCII + // | ,-- set if char[0] is non-ASCII + // v v + // mask = ... 1 1 1 0 + // ^ ^-- set if char[0] is >= 0x0800 + // `-- set if char[1] is >= 0x0800 + // + // (If the SSE4.1 code path is taken above, the meaning of the odd and even + // bits are swapped, but the logic below otherwise holds.) + // + // This means we can popcnt the number of set bits, and the result is the + // number of *additional* UTF-8 bytes that each UTF-16 code unit requires as + // it expands. This results in the wrong count for UTF-16 surrogate code + // units (we just counted that each individual code unit expands to 3 bytes, + // but in reality a well-formed UTF-16 surrogate pair expands to 4 bytes). + // We'll handle this in just a moment. + // + // For now, compute the popcnt but squirrel it away. We'll fold it in to the + // cumulative UTF-8 adjustment factor once we determine that there are no + // unpaired surrogates in our data. (Unpaired surrogates would invalidate + // our computed result and we'd have to throw it away.) + + uint popcnt = (uint)BitOperations.PopCount(mask); + + // Surrogates need to be special-cased for two reasons: (a) we need + // to account for the fact that we over-counted in the addition above; + // and (b) they require separate validation. + + utf16Data = Sse2.Add(utf16Data, vectorA800); + mask = (uint)Sse2.MoveMask(Sse2.CompareLessThan(utf16Data.AsInt16(), vector8800).AsByte()); + + if (mask != 0) + { + // There's at least one UTF-16 surrogate code unit present. + // Since we performed a pmovmskb operation on the result of a 16-bit pcmpgtw, + // the resulting bits of 'mask' will occur in pairs: + // - 00 if the corresponding UTF-16 char was not a surrogate code unit; + // - 11 if the corresponding UTF-16 char was a surrogate code unit. + // + // A UTF-16 high/low surrogate code unit has the bit pattern [ 11011q## ######## ], + // where # is any bit; q = 0 represents a high surrogate, and q = 1 represents + // a low surrogate. Since we added 0xA800 in the vectorized operation above, + // our surrogate pairs will now have the bit pattern [ 10000q## ######## ]. + // If we logical right-shift each word by 3, we'll end up with the bit pattern + // [ 00010000 q####### ], which means that we can immediately use pmovmskb to + // determine whether a given char was a high or a low surrogate. + // + // Therefore the resulting bits of 'mask2' will occur in pairs: + // - 00 if the corresponding UTF-16 char was a high surrogate code unit; + // - 01 if the corresponding UTF-16 char was a low surrogate code unit; + // - ## (garbage) if the corresponding UTF-16 char was not a surrogate code unit. + // Since 'mask' already has 00 in these positions (since the corresponding char + // wasn't a surrogate), "mask AND mask2 == 00" holds for these positions. + + uint mask2 = (uint)Sse2.MoveMask(Sse2.ShiftRightLogical(utf16Data, 3).AsByte()); + + // 'lowSurrogatesMask' has its bits occur in pairs: + // - 01 if the corresponding char was a low surrogate char, + // - 00 if the corresponding char was a high surrogate char or not a surrogate at all. + + uint lowSurrogatesMask = mask2 & mask; + + // 'highSurrogatesMask' has its bits occur in pairs: + // - 01 if the corresponding char was a high surrogate char, + // - 00 if the corresponding char was a low surrogate char or not a surrogate at all. + + uint highSurrogatesMask = (mask2 ^ 0b_0101_0101_0101_0101u /* flip all even-numbered bits 00 <-> 01 */) & mask; + + Debug.Assert((highSurrogatesMask & lowSurrogatesMask) == 0, + "A char cannot simultaneously be both a high and a low surrogate char."); + + Debug.Assert(((highSurrogatesMask | lowSurrogatesMask) & 0b_1010_1010_1010_1010u) == 0, + "Only even bits (no odd bits) of the masks should be set."); + + // Now check that each high surrogate is followed by a low surrogate and that each + // low surrogate follows a high surrogate. We make an exception for the case where + // the final char of the vector is a high surrogate, since we can't perform validation + // on it until the next iteration of the loop when we hope to consume the matching + // low surrogate. + + highSurrogatesMask <<= 2; + if ((ushort)highSurrogatesMask != lowSurrogatesMask) + { + goto NonVectorizedLoop; // error: mismatched surrogate pair; break out of vectorized logic + } + + if (highSurrogatesMask > ushort.MaxValue) + { + // There was a standalone high surrogate at the end of the vector. + // We'll adjust our counters so that we don't consider this char consumed. + + highSurrogatesMask = (ushort)highSurrogatesMask; // don't allow stray high surrogate to be consumed by popcnt + popcnt -= 2; // the '0xC000_0000' bits in the original mask are shifted out and discarded, so account for that here + pInputBuffer--; + inputLength++; + } + + // If we're 64-bit, we can perform the zero-extension of the surrogate pairs count for + // free right now, saving the extension step a few lines below. If we're 32-bit, the + // convertion to nuint immediately below is a no-op, and we'll pay the cost of the real + // 64 -bit extension a few lines below. + nuint surrogatePairsCountNuint = (uint)BitOperations.PopCount(highSurrogatesMask); + + // 2 UTF-16 chars become 1 Unicode scalar + + tempScalarCountAdjustment -= (int)surrogatePairsCountNuint; + + // Since each surrogate code unit was >= 0x0800, we eagerly assumed + // it'd be encoded as 3 UTF-8 code units, so our earlier popcnt computation + // assumes that the pair is encoded as 6 UTF-8 code units. Since each + // pair is in reality only encoded as 4 UTF-8 code units, we need to + // perform this adjustment now. + + if (IntPtr.Size == 8) + { + // Since we've already zero-extended surrogatePairsCountNuint, we can directly + // sub + sub. It's more efficient than shl + sub. + tempUtf8CodeUnitCountAdjustment -= (long)surrogatePairsCountNuint; + tempUtf8CodeUnitCountAdjustment -= (long)surrogatePairsCountNuint; + } + else + { + // Take the hit of the 64-bit extension now. + tempUtf8CodeUnitCountAdjustment -= 2 * (uint)surrogatePairsCountNuint; + } + } + + tempUtf8CodeUnitCountAdjustment += popcnt; + pInputBuffer += Vector128.Count; + inputLength -= Vector128.Count; + } while (inputLength >= Vector128.Count); + } + } + else if (Vector.IsHardwareAccelerated) + { + if (inputLength >= Vector.Count) + { + Vector vector0080 = new Vector(0x0080); + Vector vector0400 = new Vector(0x0400); + Vector vector0800 = new Vector(0x0800); + Vector vectorD800 = new Vector(0xD800); + + do + { + // The 'twoOrMoreUtf8Bytes' and 'threeOrMoreUtf8Bytes' vectors will contain + // elements whose values are 0xFFFF (-1 as signed word) iff the corresponding + // UTF-16 code unit was >= 0x0080 and >= 0x0800, respectively. By summing these + // vectors, each element of the sum will contain one of three values: + // + // 0x0000 ( 0) = original char was 0000..007F + // 0xFFFF (-1) = original char was 0080..07FF + // 0xFFFE (-2) = original char was 0800..FFFF + // + // We'll negate them to produce a value 0..2 for each element, then sum all the + // elements together to produce the number of *additional* UTF-8 code units + // required to represent this UTF-16 data. This is similar to the popcnt step + // performed by the SSE2 code path. This will overcount surrogates, but we'll + // handle that shortly. + + Vector utf16Data = Unsafe.ReadUnaligned>(pInputBuffer); + Vector twoOrMoreUtf8Bytes = Vector.GreaterThanOrEqual(utf16Data, vector0080); + Vector threeOrMoreUtf8Bytes = Vector.GreaterThanOrEqual(utf16Data, vector0800); + Vector sumVector = (Vector)(Vector.Zero - twoOrMoreUtf8Bytes - threeOrMoreUtf8Bytes); + + // We'll try summing by a natural word (rather than a 16-bit word) at a time, + // which should halve the number of operations we must perform. + + nuint popcnt = 0; + for (int i = 0; i < Vector.Count; i++) + { + popcnt += sumVector[i]; + } + + uint popcnt32 = (uint)popcnt; + if (IntPtr.Size == 8) + { + popcnt32 += (uint)(popcnt >> 32); + } + + // As in the SSE4.1 paths, compute popcnt but don't fold it in until we + // know there aren't any unpaired surrogates in the input data. + + popcnt32 = (ushort)popcnt32 + (popcnt32 >> 16); + + // Now check for surrogates. + + utf16Data -= vectorD800; + Vector surrogateChars = Vector.LessThan(utf16Data, vector0800); + if (surrogateChars != Vector.Zero) + { + // There's at least one surrogate (high or low) UTF-16 code unit in + // the vector. We'll build up additional vectors: 'highSurrogateChars' + // and 'lowSurrogateChars', where the elements are 0xFFFF iff the original + // UTF-16 code unit was a high or low surrogate, respectively. + + Vector highSurrogateChars = Vector.LessThan(utf16Data, vector0400); + Vector lowSurrogateChars = Vector.AndNot(surrogateChars, highSurrogateChars); + + // We want to make sure that each high surrogate code unit is followed by + // a low surrogate code unit and each low surrogate code unit follows a + // high surrogate code unit. Since we don't have an equivalent of pmovmskb + // or palignr available to us, we'll do this as a loop. We won't look at + // the very last high surrogate char element since we don't yet know if + // the next vector read will have a low surrogate char element. + + ushort surrogatePairsCount = 0; + for (int i = 0; i < Vector.Count - 1; i++) + { + surrogatePairsCount -= highSurrogateChars[i]; // turns into +1 or +0 + if (highSurrogateChars[i] != lowSurrogateChars[i + 1]) + { + goto NonVectorizedLoop; // error: mismatched surrogate pair; break out of vectorized logic + } + } + + if (highSurrogateChars[Vector.Count - 1] != 0) + { + // There was a standalone high surrogate at the end of the vector. + // We'll adjust our counters so that we don't consider this char consumed. + + pInputBuffer--; + inputLength++; + popcnt32 -= 2; + } + + nint surrogatePairsCountNint = (nint)surrogatePairsCount; // zero-extend to native int size + + // 2 UTF-16 chars become 1 Unicode scalar + + tempScalarCountAdjustment -= (int)surrogatePairsCountNint; + + // Since each surrogate code unit was >= 0x0800, we eagerly assumed + // it'd be encoded as 3 UTF-8 code units. Each surrogate half is only + // encoded as 2 UTF-8 code units (for 4 UTF-8 code units total), + // so we'll adjust this now. + + tempUtf8CodeUnitCountAdjustment -= surrogatePairsCountNint; + tempUtf8CodeUnitCountAdjustment -= surrogatePairsCountNint; + } + + tempUtf8CodeUnitCountAdjustment += popcnt32; + pInputBuffer += Vector.Count; + inputLength -= Vector.Count; + } while (inputLength >= Vector.Count); + } + } + + NonVectorizedLoop: + + // Vectorization isn't supported on our current platform, or the input was too small to benefit + // from vectorization, or we saw invalid UTF-16 data in the vectorized code paths and need to + // drain remaining valid chars before we report failure. + + for (; inputLength > 0; pInputBuffer++, inputLength--) + { + uint thisChar = pInputBuffer[0]; + if (thisChar <= 0x7F) + { + continue; + } + + // Bump adjustment by +1 for U+0080..U+07FF; by +2 for U+0800..U+FFFF. + // This optimistically assumes no surrogates, which we'll handle shortly. + + tempUtf8CodeUnitCountAdjustment += (thisChar + 0x0001_F800u) >> 16; + + if (!UnicodeUtility.IsSurrogateCodePoint(thisChar)) + { + continue; + } + + // Found a surrogate char. Back out the adjustment we made above, then + // try to consume the entire surrogate pair all at once. We won't bother + // trying to interpret the surrogate pair as a scalar value; we'll only + // validate that its bit pattern matches what's expected for a surrogate pair. + + tempUtf8CodeUnitCountAdjustment -= 2; + + if (inputLength == 1) + { + goto Error; // input buffer too small to read a surrogate pair + } + + thisChar = Unsafe.ReadUnaligned(pInputBuffer); + if (((thisChar - (BitConverter.IsLittleEndian ? 0xDC00_D800u : 0xD800_DC00u)) & 0xFC00_FC00u) != 0) + { + goto Error; // not a well-formed surrogate pair + } + + tempScalarCountAdjustment--; // 2 UTF-16 code units -> 1 scalar + tempUtf8CodeUnitCountAdjustment += 2; // 2 UTF-16 code units -> 4 UTF-8 code units + + pInputBuffer++; // consumed one extra char + inputLength--; + } + + Error: + + // Also used for normal return. + + utf8CodeUnitCountAdjustment = tempUtf8CodeUnitCountAdjustment; + scalarCountAdjustment = tempScalarCountAdjustment; + return pInputBuffer; + } + } +} diff --git a/src/Common/src/CoreLib/System/Text/Utf16Utility.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.cs similarity index 99% rename from src/Common/src/CoreLib/System/Text/Utf16Utility.cs rename to src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.cs index bed39057e44f..828776b4361c 100644 --- a/src/Common/src/CoreLib/System/Text/Utf16Utility.cs +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf16Utility.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; using System.Diagnostics; -namespace System.Text +namespace System.Text.Unicode { internal static partial class Utf16Utility { diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf8.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf8.cs index 6c8197d22b13..b4cae379e23a 100644 --- a/src/Common/src/CoreLib/System/Text/Unicode/Utf8.cs +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf8.cs @@ -4,6 +4,8 @@ using System.Buffers; using System.Diagnostics; +using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; namespace System.Text.Unicode { @@ -37,79 +39,87 @@ public static class Utf8 /// in will be replaced with U+FFFD in , and /// this method will not return . /// - public static OperationStatus FromUtf16(ReadOnlySpan source, Span destination, out int numCharsRead, out int numBytesWritten, bool replaceInvalidSequences = true, bool isFinalBlock = true) + public static unsafe OperationStatus FromUtf16(ReadOnlySpan source, Span destination, out int charsRead, out int bytesWritten, bool replaceInvalidSequences = true, bool isFinalBlock = true) { - int originalSourceLength = source.Length; - int originalDestinationLength = destination.Length; - OperationStatus status = OperationStatus.Done; + // Throwaway span accesses - workaround for https://github.com/dotnet/coreclr/issues/23437 - // In a loop, this is going to read and transcode one scalar value at a time - // from the source to the destination. + _ = source.Length; + _ = destination.Length; - while (!source.IsEmpty) + fixed (char* pOriginalSource = &MemoryMarshal.GetReference(source)) + fixed (byte* pOriginalDestination = &MemoryMarshal.GetReference(destination)) { - status = Rune.DecodeFromUtf16(source, out Rune firstScalarValue, out int charsConsumed); + // We're going to bulk transcode as much as we can in a loop, iterating + // every time we see bad data that requires replacement. - switch (status) + OperationStatus operationStatus = OperationStatus.Done; + char* pInputBufferRemaining = pOriginalSource; + byte* pOutputBufferRemaining = pOriginalDestination; + + while (!source.IsEmpty) { - case OperationStatus.NeedMoreData: - - // Input buffer ended with a high surrogate. Only treat this as an error - // if the caller told us that we shouldn't expect additional data in a - // future call. - - if (!isFinalBlock) - { - goto Finish; - } - - status = OperationStatus.InvalidData; - goto case OperationStatus.InvalidData; - - case OperationStatus.InvalidData: - - // Input buffer contained invalid data. If the caller told us not to - // perform U+FFFD replacement, terminate the loop immediately and return - // an error to the caller. - - if (!replaceInvalidSequences) - { - goto Finish; - } - - firstScalarValue = Rune.ReplacementChar; - goto default; - - default: - - // We know which scalar value we need to transcode to UTF-8. - // Do so now, and only terminate the loop if we ran out of space - // in the destination buffer. - - if (firstScalarValue.TryEncodeToUtf8(destination, out int bytesWritten)) - { - source = source.Slice(charsConsumed); // don't use Rune.Utf8SequenceLength; we may have performed substitution - destination = destination.Slice(bytesWritten); - status = OperationStatus.Done; // forcibly set success - continue; - } - else - { - status = OperationStatus.DestinationTooSmall; - goto Finish; - } + // We've pinned the spans at the entry point to this method. + // It's safe for us to use Unsafe.AsPointer on them during this loop. + + operationStatus = Utf8Utility.TranscodeToUtf8( + pInputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)), + inputLength: source.Length, + pOutputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)), + outputBytesRemaining: destination.Length, + pInputBufferRemaining: out pInputBufferRemaining, + pOutputBufferRemaining: out pOutputBufferRemaining); + + // If we finished the operation entirely or we ran out of space in the destination buffer, + // or if we need more input data and the caller told us that there's possibly more data + // coming, return immediately. + + if (operationStatus <= OperationStatus.DestinationTooSmall + || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock)) + { + break; + } + + // We encountered invalid data, or we need more data but the caller told us we're + // at the end of the stream. In either case treat this as truly invalid. + // If the caller didn't tell us to replace invalid sequences, return immediately. + + if (!replaceInvalidSequences) + { + operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force to be error + break; + } + + // We're going to attempt to write U+FFFD to the destination buffer. + // Do we even have enough space to do so? + + destination = destination.Slice((int)(pOutputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)))); + + if (2 >= (uint)destination.Length) + { + operationStatus = OperationStatus.DestinationTooSmall; + break; + } + + destination[0] = 0xEF; // U+FFFD = [ EF BF BD ] in UTF-8 + destination[1] = 0xBF; + destination[2] = 0xBD; + destination = destination.Slice(3); + + // Invalid UTF-16 sequences are always of length 1. Just skip the next character. + + source = source.Slice((int)(pInputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source))) + 1); + + operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the loop this is a success case + pInputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)); + pOutputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)); } - } - - Finish: - numCharsRead = originalSourceLength - source.Length; - numBytesWritten = originalDestinationLength - destination.Length; + // Not possible to make any further progress - report to our caller how far we got. - Debug.Assert((status == OperationStatus.Done) == (numCharsRead == originalSourceLength), - "Should report OperationStatus.Done if and only if we've consumed the entire input buffer."); - - return status; + charsRead = (int)(pInputBufferRemaining - pOriginalSource); + bytesWritten = (int)(pOutputBufferRemaining - pOriginalDestination); + return operationStatus; + } } /// @@ -120,79 +130,92 @@ public static OperationStatus FromUtf16(ReadOnlySpan source, Span de /// in will be replaced with U+FFFD in , and /// this method will not return . /// - public static OperationStatus ToUtf16(ReadOnlySpan source, Span destination, out int numBytesRead, out int numCharsWritten, bool replaceInvalidSequences = true, bool isFinalBlock = true) + public static unsafe OperationStatus ToUtf16(ReadOnlySpan source, Span destination, out int numBytesRead, out int numCharsWritten, bool replaceInvalidSequences = true, bool isFinalBlock = true) { - int originalSourceLength = source.Length; - int originalDestinationLength = destination.Length; - OperationStatus status = OperationStatus.Done; + // Throwaway span accesses - workaround for https://github.com/dotnet/coreclr/issues/23437 + + _ = source.Length; + _ = destination.Length; - // In a loop, this is going to read and transcode one scalar value at a time - // from the source to the destination. + // We'll be mutating these values throughout our loop. - while (!source.IsEmpty) + fixed (byte* pOriginalSource = &MemoryMarshal.GetReference(source)) + fixed (char* pOriginalDestination = &MemoryMarshal.GetReference(destination)) { - status = Rune.DecodeFromUtf8(source, out Rune firstScalarValue, out int bytesConsumed); + // We're going to bulk transcode as much as we can in a loop, iterating + // every time we see bad data that requires replacement. - switch (status) + OperationStatus operationStatus = OperationStatus.Done; + byte* pInputBufferRemaining = pOriginalSource; + char* pOutputBufferRemaining = pOriginalDestination; + + while (!source.IsEmpty) { - case OperationStatus.NeedMoreData: - - // Input buffer ended with a partial UTF-8 sequence. Only treat this as an error - // if the caller told us that we shouldn't expect additional data in a - // future call. - - if (!isFinalBlock) - { - goto Finish; - } - - status = OperationStatus.InvalidData; - goto case OperationStatus.InvalidData; - - case OperationStatus.InvalidData: - - // Input buffer contained invalid data. If the caller told us not to - // perform U+FFFD replacement, terminate the loop immediately and return - // an error to the caller. - - if (!replaceInvalidSequences) - { - goto Finish; - } - - firstScalarValue = Rune.ReplacementChar; - goto default; - - default: - - // We know which scalar value we need to transcode to UTF-16. - // Do so now, and only terminate the loop if we ran out of space - // in the destination buffer. - - if (firstScalarValue.TryEncodeToUtf16(destination, out int charsWritten)) - { - source = source.Slice(bytesConsumed); // don't use Rune.Utf16SequenceLength; we may have performed substitution - destination = destination.Slice(charsWritten); - status = OperationStatus.Done; // forcibly set success - continue; - } - else - { - status = OperationStatus.DestinationTooSmall; - goto Finish; - } + // We've pinned the spans at the entry point to this method. + // It's safe for us to use Unsafe.AsPointer on them during this loop. + + operationStatus = Utf8Utility.TranscodeToUtf16( + pInputBuffer: (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)), + inputLength: source.Length, + pOutputBuffer: (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)), + outputCharsRemaining: destination.Length, + pInputBufferRemaining: out pInputBufferRemaining, + pOutputBufferRemaining: out pOutputBufferRemaining); + + // If we finished the operation entirely or we ran out of space in the destination buffer, + // or if we need more input data and the caller told us that there's possibly more data + // coming, return immediately. + + if (operationStatus <= OperationStatus.DestinationTooSmall + || (operationStatus == OperationStatus.NeedMoreData && !isFinalBlock)) + { + break; + } + + // We encountered invalid data, or we need more data but the caller told us we're + // at the end of the stream. In either case treat this as truly invalid. + // If the caller didn't tell us to replace invalid sequences, return immediately. + + if (!replaceInvalidSequences) + { + operationStatus = OperationStatus.InvalidData; // status code may have been NeedMoreData - force to be error + break; + } + + // We're going to attempt to write U+FFFD to the destination buffer. + // Do we even have enough space to do so? + + destination = destination.Slice((int)(pOutputBufferRemaining - (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)))); + + if (destination.IsEmpty) + { + operationStatus = OperationStatus.DestinationTooSmall; + break; + } + + destination[0] = (char)UnicodeUtility.ReplacementChar; + destination = destination.Slice(1); + + // Now figure out how many bytes of the source we must skip over before we should retry + // the operation. This might be more than 1 byte. + + source = source.Slice((int)(pInputBufferRemaining - (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)))); + Debug.Assert(!source.IsEmpty, "Expected 'Done' if source is fully consumed."); + + Rune.DecodeFromUtf8(source, out _, out int bytesConsumedJustNow); + source = source.Slice(bytesConsumedJustNow); + + operationStatus = OperationStatus.Done; // we patched the error - if we're about to break out of the loop this is a success case + pInputBufferRemaining = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)); + pOutputBufferRemaining = (char*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(destination)); } - } - - Finish: - numBytesRead = originalSourceLength - source.Length; - numCharsWritten = originalDestinationLength - destination.Length; + // Not possible to make any further progress - report to our caller how far we got. - Debug.Assert((status == OperationStatus.Done) == (numBytesRead == originalSourceLength), - "Should report OperationStatus.Done if and only if we've consumed the entire input buffer."); - - return status; + numBytesRead = (int)(pInputBufferRemaining - pOriginalSource); + numCharsWritten = (int)(pOutputBufferRemaining - pOriginalDestination); + return operationStatus; + } } } } diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Helpers.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Helpers.cs new file mode 100644 index 000000000000..ab29fbe7a61d --- /dev/null +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Helpers.cs @@ -0,0 +1,863 @@ +// 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.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; +using Internal.Runtime.CompilerServices; + +namespace System.Text.Unicode +{ + internal static partial class Utf8Utility + { + /// + /// Given a machine-endian DWORD which four bytes of UTF-8 data, interprets the + /// first three bytes as a three-byte UTF-8 subsequence and returns the UTF-16 representation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractCharFromFirstThreeByteSequence(uint value) + { + Debug.Assert(UInt32BeginsWithUtf8ThreeByteMask(value)); + + if (BitConverter.IsLittleEndian) + { + // value = [ ######## | 10xxxxxx 10yyyyyy 1110zzzz ] + return ((value & 0x003F0_000u) >> 16) + | ((value & 0x0000_3F00u) >> 2) + | ((value & 0x0000_000Fu) << 12); + } + else + { + // value = [ 1110zzzz 10yyyyyy 10xxxxxx | ######## ] + return ((value & 0x0F00_0000u) >> 12) + | ((value & 0x003F_0000u) >> 10) + | ((value & 0x0000_3F00u) >> 8); + } + } + + /// + /// Given a machine-endian DWORD which four bytes of UTF-8 data, interprets the + /// first two bytes as a two-byte UTF-8 subsequence and returns the UTF-16 representation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractCharFromFirstTwoByteSequence(uint value) + { + Debug.Assert(UInt32BeginsWithUtf8TwoByteMask(value) && !UInt32BeginsWithOverlongUtf8TwoByteSequence(value)); + + if (BitConverter.IsLittleEndian) + { + // value = [ ######## ######## | 10xxxxxx 110yyyyy ] + uint leadingByte = (uint)(byte)value << 6; + return (uint)(byte)(value >> 8) + leadingByte - (0xC0u << 6) - 0x80u; // remove header bits + } + else + { + // value = [ 110yyyyy 10xxxxxx | ######## ######## ] + return (char)(((value & 0x1F00_0000u) >> 18) | ((value & 0x003F_0000u) >> 16)); + } + } + + /// + /// Given a machine-endian DWORD which four bytes of UTF-8 data, interprets the input as a + /// four-byte UTF-8 sequence and returns the machine-endian DWORD of the UTF-16 representation. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static uint ExtractCharsFromFourByteSequence(uint value) + { + if (BitConverter.IsLittleEndian) + { + if (Bmi2.IsSupported) + { + // need to reverse endianness for bit manipulation to work correctly + value = BinaryPrimitives.ReverseEndianness(value); + + // value = [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ] + // want to return [ 110110wwwwxxxxxx 110111xxxxxxxxxx ] + // where wwww = uuuuu - 1 + + uint highSurrogateChar = Bmi2.ParallelBitExtract(value, 0b00000111_00111111_00110000_00000000u); + uint lowSurrogateChar = Bmi2.ParallelBitExtract(value, 0b00000000_00000000_00001111_00111111u); + + uint combined = (lowSurrogateChar << 16) + highSurrogateChar; + combined -= 0x40u; // wwww = uuuuu - 1 + combined += 0xDC00_D800u; // add surrogate markers + return combined; + } + else + { + // input is UTF8 [ 10xxxxxx 10yyyyyy 10uuzzzz 11110uuu ] = scalar 000uuuuu zzzzyyyy yyxxxxxx + // want to return UTF16 scalar 000uuuuuzzzzyyyyyyxxxxxx = [ 110111yy yyxxxxxx 110110ww wwzzzzyy ] + // where wwww = uuuuu - 1 + uint retVal = (uint)(byte)value << 8; // retVal = [ 00000000 00000000 11110uuu 00000000 ] + retVal |= (value & 0x0000_3F00u) >> 6; // retVal = [ 00000000 00000000 11110uuu uuzzzz00 ] + retVal |= (value & 0x0030_0000u) >> 20; // retVal = [ 00000000 00000000 11110uuu uuzzzzyy ] + retVal |= (value & 0x3F00_0000u) >> 8; // retVal = [ 00000000 00xxxxxx 11110uuu uuzzzzyy ] + retVal |= (value & 0x000F_0000u) << 6; // retVal = [ 000000yy yyxxxxxx 11110uuu uuzzzzyy ] + retVal -= 0x0000_0040u; // retVal = [ 000000yy yyxxxxxx 111100ww wwzzzzyy ] + retVal -= 0x0000_2000u; // retVal = [ 000000yy yyxxxxxx 110100ww wwzzzzyy ] + retVal += 0x0000_0800u; // retVal = [ 000000yy yyxxxxxx 110110ww wwzzzzyy ] + retVal += 0xDC00_0000u; // retVal = [ 110111yy yyxxxxxx 110110ww wwzzzzyy ] + return retVal; + } + } + else + { + // input is UTF8 [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ] = scalar 000uuuuu zzzzyyyy yyxxxxxx + // want to return UTF16 scalar 000uuuuuxxxxxxxxxxxxxxxx = [ 110110wwwwxxxxxx 110111xxxxxxxxx ] + // where wwww = uuuuu - 1 + uint retVal = value & 0xFF00_0000u; // retVal = [ 11110uuu 00000000 00000000 00000000 ] + retVal |= (value & 0x003F_0000u) << 2; // retVal = [ 11110uuu uuzzzz00 00000000 00000000 ] + retVal |= (value & 0x0000_3000u) << 4; // retVal = [ 11110uuu uuzzzzyy 00000000 00000000 ] + retVal |= (value & 0x0000_0F00u) >> 2; // retVal = [ 11110uuu uuzzzzyy 000000yy yy000000 ] + retVal |= (value & 0x0000_003Fu); // retVal = [ 11110uuu uuzzzzyy 000000yy yyxxxxxx ] + retVal -= 0x2000_0000u; // retVal = [ 11010uuu uuzzzzyy 000000yy yyxxxxxx ] + retVal -= 0x0040_0000u; // retVal = [ 110100ww wwzzzzyy 000000yy yyxxxxxx ] + retVal += 0x0000_DC00u; // retVal = [ 110100ww wwzzzzyy 110111yy yyxxxxxx ] + retVal += 0x0800_0000u; // retVal = [ 110110ww wwzzzzyy 110111yy yyxxxxxx ] + return retVal; + } + } + + /// + /// Given a 32-bit integer that represents a valid packed UTF-16 surrogate pair, all in machine-endian order, + /// returns the packed 4-byte UTF-8 representation of this scalar value, also in machine-endian order. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractFourUtf8BytesFromSurrogatePair(uint value) + { + Debug.Assert(IsWellFormedUtf16SurrogatePair(value)); + + if (BitConverter.IsLittleEndian) + { + // input = [ 110111yyyyxxxxxx 110110wwwwzzzzyy ] = scalar (000uuuuu zzzzyyyy yyxxxxxx) + // must return [ 10xxxxxx 10yyyyyy 10uuzzzz 11110uuu ], where wwww = uuuuu - 1 + + if (Bmi2.IsSupported) + { + // Since pdep and pext have high latencies and can only be dispatched to a single execution port, we want + // to use them conservatively. Here, we'll build up the scalar value (this would normally be pext) via simple + // logical and arithmetic operations, and use only pdep for the expensive step of exploding the scalar across + // all four output bytes. + + uint unmaskedScalar = (value << 10) + (value >> 16) + ((0x40u) << 10) /* uuuuu = wwww + 1 */ - 0xDC00u /* remove low surrogate marker */; + + // Now, unmaskedScalar = [ xxxxxx11 011uuuuu zzzzyyyy yyxxxxxx ]. There's a bit of unneeded junk at the beginning + // that should normally be masked out via an and, but we'll just direct pdep to ignore it. + + uint exploded = Bmi2.ParallelBitDeposit(unmaskedScalar, 0b00000111_00111111_00111111_00111111u); // = [ 00000uuu 00uuzzzz 00yyyyyy 00xxxxxx ] + return BinaryPrimitives.ReverseEndianness(exploded + 0xF080_8080u); // = [ 10xxxxxx 10yyyyyy 10uuzzzz 11110uuu ] + } + else + { + value += 0x0000_0040u; // = [ 110111yyyyxxxxxx 11011uuuuuzzzzyy ] + + uint tempA = BinaryPrimitives.ReverseEndianness(value & 0x003F_0700u); // = [ 00000000 00000uuu 00xxxxxx 00000000 ] + tempA = BitOperations.RotateLeft(tempA, 16); // = [ 00xxxxxx 00000000 00000000 00000uuu ] + + uint tempB = (value & 0x00FCu) << 6; // = [ 00000000 00000000 00uuzzzz 00000000 ] + uint tempC = (value >> 6) & 0x000F_0000u; // = [ 00000000 0000yyyy 00000000 00000000 ] + tempC |= tempB; + + uint tempD = (value & 0x03u) << 20; // = [ 00000000 00yy0000 00000000 00000000 ] + tempD |= 0x8080_80F0u; + + return (tempD | tempA | tempC); // = [ 10xxxxxx 10yyyyyy 10uuzzzz 11110uuu ] + } + } + else + { + // input = [ 110110wwwwzzzzyy 110111yyyyxxxxxx ], where wwww = uuuuu - 1 + // must return [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ], where wwww = uuuuu - 1 + + value -= 0xD800_DC00u; // = [ 000000wwwwzzzzyy 000000yyyyxxxxxx ] + value += 0x0040_0000u; // = [ 00000uuuuuzzzzyy 000000yyyyxxxxxx ] + + uint tempA = value & 0x0700_0000u; // = [ 00000uuu 00000000 00000000 00000000 ] + uint tempB = (value >> 2) & 0x003F_0000u; // = [ 00000000 00uuzzzz 00000000 00000000 ] + tempB |= tempA; + + uint tempC = (value << 2) & 0x0000_0F00u; // = [ 00000000 00000000 0000yyyy 00000000 ] + uint tempD = (value >> 6) & 0x0003_0000u; // = [ 00000000 00000000 00yy0000 00000000 ] + tempD |= tempC; + + uint tempE = (value & 0x3Fu) + 0xF080_8080u; // = [ 11110000 10000000 10000000 10xxxxxx ] + return (tempE | tempB | tempD); // = [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ] + } + } + + /// + /// Given a machine-endian DWORD which represents two adjacent UTF-8 two-byte sequences, + /// returns the machine-endian DWORD representation of that same data as two adjacent + /// UTF-16 byte sequences. + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractTwoCharsPackedFromTwoAdjacentTwoByteSequences(uint value) + { + // We don't want to swap the position of the high and low WORDs, + // as the buffer was read in machine order and will be written in + // machine order. + + if (BitConverter.IsLittleEndian) + { + // value = [ 10xxxxxx 110yyyyy | 10xxxxxx 110yyyyy ] + return ((value & 0x3F003F00u) >> 8) | ((value & 0x001F001Fu) << 6); + } + else + { + // value = [ 110yyyyy 10xxxxxx | 110yyyyy 10xxxxxx ] + return ((value & 0x1F001F00u) >> 2) | (value & 0x003F003Fu); + } + } + + /// + /// Given a machine-endian DWORD which represents two adjacent UTF-16 sequences, + /// returns the machine-endian DWORD representation of that same data as two + /// adjacent UTF-8 two-byte sequences. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractTwoUtf8TwoByteSequencesFromTwoPackedUtf16Chars(uint value) + { + // stays in machine endian + + Debug.Assert(IsFirstCharTwoUtf8Bytes(value) && IsSecondCharTwoUtf8Bytes(value)); + + if (BitConverter.IsLittleEndian) + { + // value = [ 00000YYY YYXXXXXX 00000yyy yyxxxxxx ] + // want to return [ 10XXXXXX 110YYYYY 10xxxxxx 110yyyyy ] + + return ((value >> 6) & 0x001F_001Fu) + ((value << 8) & 0x3F00_3F00u) + 0x80C0_80C0u; + } + else + { + // value = [ 00000YYY YYXXXXXX 00000yyy yyxxxxxx ] + // want to return [ 110YYYYY 10XXXXXX 110yyyyy 10xxxxxx ] + + return ((value << 2) & 0x1F00_1F00u) + (value & 0x003F_003Fu) + 0xC080_C080u; + } + } + + /// + /// Given a machine-endian DWORD which represents two adjacent UTF-16 sequences, + /// returns the machine-endian DWORD representation of the first UTF-16 char + /// as a UTF-8 two-byte sequence packed into a WORD and zero-extended to DWORD. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ExtractUtf8TwoByteSequenceFromFirstUtf16Char(uint value) + { + // stays in machine endian + + Debug.Assert(IsFirstCharTwoUtf8Bytes(value)); + + if (BitConverter.IsLittleEndian) + { + // value = [ ######## ######## 00000yyy yyxxxxxx ] + // want to return [ ######## ######## 10xxxxxx 110yyyyy ] + + uint temp = (value << 2) & 0x1F00u; // [ 00000000 00000000 000yyyyy 00000000 ] + value &= 0x3Fu; // [ 00000000 00000000 00000000 00xxxxxx ] + return BinaryPrimitives.ReverseEndianness((ushort)(temp + value + 0xC080u)); // [ 00000000 00000000 10xxxxxx 110yyyyy ] + } + else + { + // value = [ 00000yyy yyxxxxxx ######## ######## ] + // want to return [ ######## ######## 110yyyyy 10xxxxxx ] + + uint temp = (value >> 16) & 0x3Fu; // [ 00000000 00000000 00000000 00xxxxxx ] + value = (value >> 22) & 0x1F00u; // [ 00000000 00000000 000yyyyy 0000000 ] + return value + temp + 0xC080u; + } + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the first UTF-16 character is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsFirstCharAscii(uint value) + { + // Little-endian: Given [ #### AAAA ], return whether AAAA is in range [ 0000..007F ]. + // Big-endian: Given [ AAAA #### ], return whether AAAA is in range [ 0000..007F ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (value & 0xFF80u) == 0) + || (!BitConverter.IsLittleEndian && value < 0x0080_0000u); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the first UTF-16 character requires *at least* 3 bytes to encode in UTF-8. + /// This also returns true if the first UTF-16 character is a surrogate character (well-formedness is not validated). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsFirstCharAtLeastThreeUtf8Bytes(uint value) + { + // Little-endian: Given [ #### AAAA ], return whether AAAA is in range [ 0800..FFFF ]. + // Big-endian: Given [ AAAA #### ], return whether AAAA is in range [ 0800..FFFF ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (value & 0xF800u) != 0) + || (!BitConverter.IsLittleEndian && value >= 0x0800_0000u); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the first UTF-16 character is a surrogate character (either high or low). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsFirstCharSurrogate(uint value) + { + // Little-endian: Given [ #### AAAA ], return whether AAAA is in range [ D800..DFFF ]. + // Big-endian: Given [ AAAA #### ], return whether AAAA is in range [ D800..DFFF ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value - 0xD800u) & 0xF800u) == 0) + || (!BitConverter.IsLittleEndian && (value - 0xD800_0000u) < 0x0800_0000u); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the first UTF-16 character would be encoded as exactly 2 bytes in UTF-8. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsFirstCharTwoUtf8Bytes(uint value) + { + // Little-endian: Given [ #### AAAA ], return whether AAAA is in range [ 0080..07FF ]. + // Big-endian: Given [ AAAA #### ], return whether AAAA is in range [ 0080..07FF ]. + + // TODO: I'd like to be able to write "(ushort)(value - 0x0080u) < 0x0780u" for the little-endian + // case, but the JIT doesn't currently emit 16-bit comparisons efficiently. + // Tracked as https://github.com/dotnet/coreclr/issues/18022. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value - 0x0080u) & 0xFFFFu) < 0x0780u) + || (!BitConverter.IsLittleEndian && UnicodeUtility.IsInRangeInclusive(value, 0x0080_0000u, 0x07FF_FFFFu)); + } + + /// + /// Returns iff the low byte of + /// is a UTF-8 continuation byte. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsLowByteUtf8ContinuationByte(uint value) + { + // The JIT won't emit a single 8-bit signed cmp instruction (see IsUtf8ContinuationByte), + // so the best we can do for now is the lea / cmp pair. + // Tracked as https://github.com/dotnet/coreclr/issues/18022. + + return (byte)(value - 0x80u) <= 0x3Fu; + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the second UTF-16 character is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSecondCharAscii(uint value) + { + // Little-endian: Given [ BBBB #### ], return whether BBBB is in range [ 0000..007F ]. + // Big-endian: Given [ #### BBBB ], return whether BBBB is in range [ 0000..007F ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && value < 0x0080_0000u) + || (!BitConverter.IsLittleEndian && (value & 0xFF80u) == 0); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the second UTF-16 character requires *at least* 3 bytes to encode in UTF-8. + /// This also returns true if the second UTF-16 character is a surrogate character (well-formedness is not validated). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSecondCharAtLeastThreeUtf8Bytes(uint value) + { + // Little-endian: Given [ BBBB #### ], return whether BBBB is in range [ 0800..FFFF ]. + // Big-endian: Given [ #### BBBB ], return whether ABBBBAAA is in range [ 0800..FFFF ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (value & 0xF800_0000u) != 0) + || (!BitConverter.IsLittleEndian && (value & 0xF800u) != 0); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the second UTF-16 character is a surrogate character (either high or low). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSecondCharSurrogate(uint value) + { + // Little-endian: Given [ BBBB #### ], return whether BBBB is in range [ D800..DFFF ]. + // Big-endian: Given [ #### BBBB ], return whether BBBB is in range [ D800..DFFF ]. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (value - 0xD800_0000u) < 0x0800_0000u) + || (!BitConverter.IsLittleEndian && ((value - 0xD800u) & 0xF800u) == 0); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the second UTF-16 character would be encoded as exactly 2 bytes in UTF-8. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSecondCharTwoUtf8Bytes(uint value) + { + // Little-endian: Given [ BBBB #### ], return whether BBBB is in range [ 0080..07FF ]. + // Big-endian: Given [ #### BBBB ], return whether BBBB is in range [ 0080..07FF ]. + + // TODO: I'd like to be able to write "(ushort)(value - 0x0080u) < 0x0780u" for the big-endian + // case, but the JIT doesn't currently emit 16-bit comparisons efficiently. + // Tracked as https://github.com/dotnet/coreclr/issues/18022. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && UnicodeUtility.IsInRangeInclusive(value, 0x0080_0000u, 0x07FF_FFFFu)) + || (!BitConverter.IsLittleEndian && ((value - 0x0080u) & 0xFFFFu) < 0x0780u); + } + + /// + /// Returns iff is a UTF-8 continuation byte; + /// i.e., has binary representation 10xxxxxx, where x is any bit. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsUtf8ContinuationByte(in byte value) + { + // This API takes its input as a readonly ref so that the JIT can emit "cmp ModRM" statements + // directly rather than bounce a temporary through a register. That is, we want the JIT to be + // able to emit a single "cmp byte ptr [data], C0h" statement if we're querying a memory location + // to see if it's a continuation byte. Data that's already enregistered will go through the + // normal "cmp reg, C0h" code paths, perhaps with some extra unnecessary "movzx" instructions. + // + // The below check takes advantage of the two's complement representation of negative numbers. + // [ 0b1000_0000, 0b1011_1111 ] is [ -127 (sbyte.MinValue), -65 ] + + return ((sbyte)value < -64); + } + + /// + /// Given a 32-bit integer that represents two packed UTF-16 characters, all in machine-endian order, + /// returns true iff the two characters represent a well-formed UTF-16 surrogate pair. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsWellFormedUtf16SurrogatePair(uint value) + { + // Little-endian: Given [ LLLL HHHH ], validate that LLLL in [ DC00..DFFF ] and HHHH in [ D800..DBFF ]. + // Big-endian: Given [ HHHH LLLL ], validate that HHHH in [ D800..DBFF ] and LLLL in [ DC00..DFFF ]. + // + // We're essentially performing a range check on each component of the input in parallel. The allowed range + // ends up being "< 0x0400" after the beginning of the allowed range is subtracted from each element. We + // can't perform the equivalent of two CMPs in parallel, but we can take advantage of the fact that 0x0400 + // is a whole power of 2, which means that a CMP is really just a glorified TEST operation. Two TESTs *can* + // be performed in parallel. The logic below then becomes 3 operations: "add/lea; test; jcc". + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value - 0xDC00_D800u) & 0xFC00_FC00u) == 0) + || (!BitConverter.IsLittleEndian && ((value - 0xD800_DC00u) & 0xFC00_FC00u) == 0); + } + + /// + /// Converts a DWORD from machine-endian to little-endian. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint ToLittleEndian(uint value) + { + if (BitConverter.IsLittleEndian) + { + return value; + } + else + { + return BinaryPrimitives.ReverseEndianness(value); + } + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first two bytes of the buffer are + /// an overlong representation of a sequence that should be represented as one byte. + /// This method *does not* validate that the sequence matches the appropriate + /// 2-byte sequence mask (see ). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32BeginsWithOverlongUtf8TwoByteSequence(uint value) + { + // ASSUMPTION: Caller has already checked the '110yyyyy 10xxxxxx' mask of the input. + Debug.Assert(UInt32BeginsWithUtf8TwoByteMask(value)); + + // Per Table 3-7, first byte of two-byte sequence must be within range C2 .. DF. + // Since we already validated it's 80 <= ?? <= DF (per mask check earlier), now only need + // to check that it's < C2. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((byte)value < 0xC2u)) + || (!BitConverter.IsLittleEndian && (value < 0xC200_0000u)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first four bytes of the buffer match + /// the UTF-8 4-byte sequence mask [ 11110www 10zzzzzz 10yyyyyy 10xxxxxx ]. This + /// method *does not* validate that the sequence is well-formed; the caller must + /// still perform overlong form or out-of-range checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32BeginsWithUtf8FourByteMask(uint value) + { + // The code in this method is equivalent to the code + // below but is slightly more optimized. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0xC0C0C0F8U; + // const uint comparand = 0x808080F0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xF8C0C0C0U; + // const uint comparand = 0xF0808000U; + // return ((value & mask) == comparand); + // } + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (((value - 0x8080_80F0u) & 0xC0C0_C0F8u) == 0)) + || (!BitConverter.IsLittleEndian && (((value - 0xF080_8000u) & 0xF8C0_C0C0u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first three bytes of the buffer match + /// the UTF-8 3-byte sequence mask [ 1110zzzz 10yyyyyy 10xxxxxx ]. This method *does not* + /// validate that the sequence is well-formed; the caller must still perform + /// overlong form or surrogate checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32BeginsWithUtf8ThreeByteMask(uint value) + { + // The code in this method is equivalent to the code + // below but is slightly more optimized. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0x00C0C0F0U; + // const uint comparand = 0x008080E0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xF0C0C000U; + // const uint comparand = 0xE0808000U; + // return ((value & mask) == comparand); + // } + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (((value - 0x0080_80E0u) & 0x00C0_C0F0u) == 0)) + || (!BitConverter.IsLittleEndian && (((value - 0xE080_8000u) & 0xF0C0_C000u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first two bytes of the buffer match + /// the UTF-8 2-byte sequence mask [ 110yyyyy 10xxxxxx ]. This method *does not* + /// validate that the sequence is well-formed; the caller must still perform + /// overlong form checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32BeginsWithUtf8TwoByteMask(uint value) + { + // The code in this method is equivalent to the code + // below but is slightly more optimized. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0x0000C0E0U; + // const uint comparand = 0x000080C0U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0xE0C00000U; + // const uint comparand = 0xC0800000U; + // return ((value & mask) == comparand); + // } + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (((value - 0x0000_80C0u) & 0x0000_C0E0u) == 0)) + || (!BitConverter.IsLittleEndian && (((value - 0xC080_0000u) & 0xE0C0_0000u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first two bytes of the buffer are + /// an overlong representation of a sequence that should be represented as one byte. + /// This method *does not* validate that the sequence matches the appropriate + /// 2-byte sequence mask (see ). + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32EndsWithOverlongUtf8TwoByteSequence(uint value) + { + // ASSUMPTION: Caller has already checked the '110yyyyy 10xxxxxx' mask of the input. + Debug.Assert(UInt32EndsWithUtf8TwoByteMask(value)); + + // Per Table 3-7, first byte of two-byte sequence must be within range C2 .. DF. + // We already validated that it's 80 .. DF (per mask check earlier). + // C2 = 1100 0010 + // DF = 1101 1111 + // This means that we can AND the leading byte with the mask 0001 1110 (1E), + // and if the result is zero the sequence is overlong. + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value & 0x001E_0000u) == 0)) + || (!BitConverter.IsLittleEndian && ((value & 0x1E00u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the last two bytes of the buffer match + /// the UTF-8 2-byte sequence mask [ 110yyyyy 10xxxxxx ]. This method *does not* + /// validate that the sequence is well-formed; the caller must still perform + /// overlong form checking. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32EndsWithUtf8TwoByteMask(uint value) + { + // The code in this method is equivalent to the code + // below but is slightly more optimized. + // + // if (BitConverter.IsLittleEndian) + // { + // const uint mask = 0xC0E00000U; + // const uint comparand = 0x80C00000U; + // return ((value & mask) == comparand); + // } + // else + // { + // const uint mask = 0x0000E0C0U; + // const uint comparand = 0x0000C080U; + // return ((value & mask) == comparand); + // } + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && (((value - 0x80C0_0000u) & 0xC0E0_0000u) == 0)) + || (!BitConverter.IsLittleEndian && (((value - 0x0000_C080u) & 0x0000_E0C0u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD on a little-endian machine, + /// returns iff the first two bytes of the buffer are a well-formed + /// UTF-8 two-byte sequence. This wraps the mask check and the overlong check into a + /// single operation. Returns if running on a big-endian machine. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32BeginsWithValidUtf8TwoByteSequenceLittleEndian(uint value) + { + // Per Table 3-7, valid 2-byte sequences are [ C2..DF ] [ 80..BF ]. + // In little-endian, that would be represented as: + // [ ######## ######## 10xxxxxx 110yyyyy ]. + // Due to the little-endian representation we can perform a trick by ANDing the low + // WORD with the bitmask [ 11000000 11111111 ] and checking that the value is within + // the range [ 10000000_11000010, 10000000_11011111 ]. This performs both the + // 2-byte-sequence bitmask check and overlong form validation with one comparison. + + Debug.Assert(BitConverter.IsLittleEndian); + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && UnicodeUtility.IsInRangeInclusive(value & 0xC0FFu, 0x80C2u, 0x80DFu)) + || (!BitConverter.IsLittleEndian && false); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD on a little-endian machine, + /// returns iff the last two bytes of the buffer are a well-formed + /// UTF-8 two-byte sequence. This wraps the mask check and the overlong check into a + /// single operation. Returns if running on a big-endian machine. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32EndsWithValidUtf8TwoByteSequenceLittleEndian(uint value) + { + // See comments in UInt32BeginsWithValidUtf8TwoByteSequenceLittleEndian. + + Debug.Assert(BitConverter.IsLittleEndian); + + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && UnicodeUtility.IsInRangeInclusive(value & 0xC0FF_0000u, 0x80C2_0000u, 0x80DF_0000u)) + || (!BitConverter.IsLittleEndian && false); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the first byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32FirstByteIsAscii(uint value) + { + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value & 0x80u) == 0)) + || (!BitConverter.IsLittleEndian && ((int)value >= 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the fourth byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32FourthByteIsAscii(uint value) + { + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((int)value >= 0)) + || (!BitConverter.IsLittleEndian && ((value & 0x80u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the second byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32SecondByteIsAscii(uint value) + { + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value & 0x8000u) == 0)) + || (!BitConverter.IsLittleEndian && ((value & 0x0080_0000u) == 0)); + } + + /// + /// Given a UTF-8 buffer which has been read into a DWORD in machine endianness, + /// returns iff the third byte of the buffer is ASCII. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool UInt32ThirdByteIsAscii(uint value) + { + // Return statement is written this way to work around https://github.com/dotnet/coreclr/issues/914. + + return (BitConverter.IsLittleEndian && ((value & 0x0080_0000u) == 0)) + || (!BitConverter.IsLittleEndian && ((value & 0x8000u) == 0)); + } + + /// + /// Given a DWORD which represents a buffer of 4 ASCII bytes, widen each byte to a 16-bit WORD + /// and writes the resulting QWORD into the destination with machine endianness. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void Widen4AsciiBytesToCharsAndWrite(ref char outputBuffer, uint value) + { + if (Bmi2.X64.IsSupported) + { + // BMI2 will work regardless of the processor's endianness. + Unsafe.WriteUnaligned(ref Unsafe.As(ref outputBuffer), Bmi2.X64.ParallelBitDeposit(value, 0x00FF00FF_00FF00FFul)); + } + else + { + if (BitConverter.IsLittleEndian) + { + outputBuffer = (char)(byte)value; + value >>= 8; + Unsafe.Add(ref outputBuffer, 1) = (char)(byte)value; + value >>= 8; + Unsafe.Add(ref outputBuffer, 2) = (char)(byte)value; + value >>= 8; + Unsafe.Add(ref outputBuffer, 3) = (char)value; + } + else + { + Unsafe.Add(ref outputBuffer, 3) = (char)(byte)value; + value >>= 8; + Unsafe.Add(ref outputBuffer, 2) = (char)(byte)value; + value >>= 8; + Unsafe.Add(ref outputBuffer, 1) = (char)(byte)value; + value >>= 8; + outputBuffer = (char)value; + } + } + } + + /// + /// Given a DWORD which represents a buffer of 2 packed UTF-16 values in machine endianess, + /// converts those scalar values to their 3-byte UTF-8 representation and writes the + /// resulting 6 bytes to the destination buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteTwoUtf16CharsAsTwoUtf8ThreeByteSequences(ref byte outputBuffer, uint value) + { + Debug.Assert(IsFirstCharAtLeastThreeUtf8Bytes(value) && !IsFirstCharSurrogate(value), "First half of value should've been 0800..D7FF or E000..FFFF"); + Debug.Assert(IsSecondCharAtLeastThreeUtf8Bytes(value) && !IsSecondCharSurrogate(value), "Second half of value should've been 0800..D7FF or E000..FFFF"); + + if (BitConverter.IsLittleEndian) + { + // value = [ ZZZZYYYY YYXXXXXX zzzzyyyy yyxxxxxx ] + // want to write [ 1110ZZZZ 10xxxxxx 10yyyyyy 1110zzzz ] [ 10XXXXXX 10YYYYYY ] + + uint tempA = ((value << 2) & 0x3F00u) | ((value & 0x3Fu) << 16); // = [ 00000000 00xxxxxx 00yyyyyy 00000000 ] + uint tempB = ((value >> 4) & 0x0F00_0000u) | ((value >> 12) & 0x0Fu); // = [ 0000ZZZZ 00000000 00000000 0000zzzz ] + Unsafe.WriteUnaligned(ref outputBuffer, tempA + tempB + 0xE080_80E0u); // = [ 1110ZZZZ 10xxxxxx 10yyyyyy 1110zzzz ] + Unsafe.WriteUnaligned(ref Unsafe.Add(ref outputBuffer, 4), (ushort)(((value >> 22) & 0x3Fu) + ((value >> 8) & 0x3F00u) + 0x8080u)); // = [ 10XXXXXX 10YYYYYY ] + } + else + { + // value = [ zzzzyyyy yyxxxxxx ZZZZYYYY YYXXXXXX ] + // want to write [ 1110zzzz ] [ 10yyyyyy ] [ 10xxxxxx ] [ 1110ZZZZ ] [ 10YYYYYY ] [ 10XXXXXX ] + + Unsafe.Add(ref outputBuffer, 5) = (byte)((value & 0x3Fu) | 0x80u); + Unsafe.Add(ref outputBuffer, 4) = (byte)(((value >>= 6) & 0x3Fu) | 0x80u); + Unsafe.Add(ref outputBuffer, 3) = (byte)(((value >>= 6) & 0x0Fu) | 0xE0u); + Unsafe.Add(ref outputBuffer, 2) = (byte)(((value >>= 4) & 0x3Fu) | 0x80u); + Unsafe.Add(ref outputBuffer, 1) = (byte)(((value >>= 6) & 0x3Fu) | 0x80u); + outputBuffer = (byte)((value >>= 6) | 0xE0u); + } + } + + + /// + /// Given a DWORD which represents a buffer of 2 packed UTF-16 values in machine endianess, + /// converts the first UTF-16 value to its 3-byte UTF-8 representation and writes the + /// resulting 3 bytes to the destination buffer. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void WriteFirstUtf16CharAsUtf8ThreeByteSequence(ref byte outputBuffer, uint value) + { + Debug.Assert(IsFirstCharAtLeastThreeUtf8Bytes(value) && !IsFirstCharSurrogate(value), "First half of value should've been 0800..D7FF or E000..FFFF"); + + if (BitConverter.IsLittleEndian) + { + // value = [ ######## ######## zzzzyyyy yyxxxxxx ] + // want to write [ 10yyyyyy 1110zzzz ] [ 10xxxxxx ] + + uint tempA = (value << 2) & 0x3F00u; // [ 00yyyyyy 00000000 ] + uint tempB = ((uint)(ushort)value >> 12); // [ 00000000 0000zzzz ] + Unsafe.WriteUnaligned(ref outputBuffer, (ushort)(tempA + tempB + 0x80E0u)); // [ 10yyyyyy 1110zzzz ] + Unsafe.Add(ref outputBuffer, 2) = (byte)((value & 0x3Fu) | ~0x7Fu); // [ 10xxxxxx ] + } + else + { + // value = [ zzzzyyyy yyxxxxxx ######## ######## ] + // want to write [ 1110zzzz ] [ 10yyyyyy ] [ 10xxxxxx ] + + Unsafe.Add(ref outputBuffer, 2) = (byte)(((value >>= 16) & 0x3Fu) | 0x80u); + Unsafe.Add(ref outputBuffer, 1) = (byte)(((value >>= 6) & 0x3Fu) | 0x80u); + outputBuffer = (byte)((value >>= 6) | 0xE0u); + } + } + } +} diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Transcoding.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Transcoding.cs new file mode 100644 index 000000000000..3b83a2455914 --- /dev/null +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Transcoding.cs @@ -0,0 +1,1480 @@ +// 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.Buffers; +using System.Buffers.Binary; +using System.Diagnostics; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; +using Internal.Runtime.CompilerServices; + +#if BIT64 +using nint = System.Int64; +using nuint = System.UInt64; +#else // BIT64 +using nint = System.Int32; +using nuint = System.UInt32; +#endif // BIT64 + +namespace System.Text.Unicode +{ + internal static unsafe partial class Utf8Utility + { +#if DEBUG + static Utf8Utility() + { + Debug.Assert(sizeof(nint) == IntPtr.Size && nint.MinValue < 0, "nint is defined incorrectly."); + Debug.Assert(sizeof(nuint) == IntPtr.Size && nuint.MinValue == 0, "nuint is defined incorrectly."); + + _ValidateAdditionalNIntDefinitions(); + } +#endif // DEBUG + + // On method return, pInputBufferRemaining and pOutputBufferRemaining will both point to where + // the next byte would have been consumed from / the next char would have been written to. + // inputLength in bytes, outputCharsRemaining in chars. + [MethodImpl(MethodImplOptions.AggressiveOptimization)] + public static OperationStatus TranscodeToUtf16(byte* pInputBuffer, int inputLength, char* pOutputBuffer, int outputCharsRemaining, out byte* pInputBufferRemaining, out char* pOutputBufferRemaining) + { + Debug.Assert(inputLength >= 0, "Input length must not be negative."); + Debug.Assert(pInputBuffer != null || inputLength == 0, "Input length must be zero if input buffer pointer is null."); + + Debug.Assert(outputCharsRemaining >= 0, "Destination length must not be negative."); + Debug.Assert(pOutputBuffer != null || outputCharsRemaining == 0, "Destination length must be zero if destination buffer pointer is null."); + + // First, try vectorized conversion. + + { + nuint numElementsConverted = ASCIIUtility.WidenAsciiToUtf16(pInputBuffer, pOutputBuffer, (uint)Math.Min(inputLength, outputCharsRemaining)); + + pInputBuffer += numElementsConverted; + pOutputBuffer += numElementsConverted; + + // Quick check - did we just end up consuming the entire input buffer? + // If so, short-circuit the remainder of the method. + + if ((int)numElementsConverted == inputLength) + { + pInputBufferRemaining = pInputBuffer; + pOutputBufferRemaining = pOutputBuffer; + return OperationStatus.Done; + } + + inputLength -= (int)numElementsConverted; + outputCharsRemaining -= (int)numElementsConverted; + } + + if (inputLength < sizeof(uint)) + { + goto ProcessInputOfLessThanDWordSize; + } + + byte* pFinalPosWhereCanReadDWordFromInputBuffer = pInputBuffer + (uint)inputLength - 4; + + // Begin the main loop. + +#if DEBUG + byte* pLastBufferPosProcessed = null; // used for invariant checking in debug builds +#endif + + while (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + // Read 32 bits at a time. This is enough to hold any possible UTF8-encoded scalar. + + uint thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + AfterReadDWord: + +#if DEBUG + Debug.Assert(pLastBufferPosProcessed < pInputBuffer, "Algorithm should've made forward progress since last read."); + pLastBufferPosProcessed = pInputBuffer; +#endif + // First, check for the common case of all-ASCII bytes. + + if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) + { + // We read an all-ASCII sequence. + + if (outputCharsRemaining < sizeof(uint)) + { + goto ProcessRemainingBytesSlow; // running out of space, but may be able to write some data + } + + Widen4AsciiBytesToCharsAndWrite(ref *pOutputBuffer, thisDWord); + pInputBuffer += 4; + pOutputBuffer += 4; + outputCharsRemaining -= 4; + + // If we saw a sequence of all ASCII, there's a good chance a significant amount of following data is also ASCII. + // Below is basically unrolled loops with poor man's vectorization. + + uint remainingInputBytes = (uint)(void*)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) + 4; + uint maxIters = Math.Min(remainingInputBytes, (uint)outputCharsRemaining) / (2 * sizeof(uint)); + uint secondDWord; + int i; + for (i = 0; (uint)i < maxIters; i++) + { + // Reading two DWORDs in parallel benchmarked faster than reading a single QWORD. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + secondDWord = Unsafe.ReadUnaligned(pInputBuffer + sizeof(uint)); + + if (!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord | secondDWord)) + { + goto LoopTerminatedEarlyDueToNonAsciiData; + } + + pInputBuffer += 8; + + Widen4AsciiBytesToCharsAndWrite(ref pOutputBuffer[0], thisDWord); + Widen4AsciiBytesToCharsAndWrite(ref pOutputBuffer[4], secondDWord); + + pOutputBuffer += 8; + } + + outputCharsRemaining -= 8 * i; + + continue; // need to perform a bounds check because we might be running out of data + + LoopTerminatedEarlyDueToNonAsciiData: + + if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) + { + // The first DWORD contained all-ASCII bytes, so expand it. + + Widen4AsciiBytesToCharsAndWrite(ref *pOutputBuffer, thisDWord); + + // continue the outer loop from the second DWORD + + Debug.Assert(!ASCIIUtility.AllBytesInUInt32AreAscii(secondDWord)); + thisDWord = secondDWord; + + pInputBuffer += 4; + pOutputBuffer += 4; + outputCharsRemaining -= 4; + } + + outputCharsRemaining -= 8 * i; + + // We know that there's *at least* one DWORD of data remaining in the buffer. + // We also know that it's not all-ASCII. We can skip the logic at the beginning of the main loop. + + goto AfterReadDWordSkipAllBytesAsciiCheck; + } + + AfterReadDWordSkipAllBytesAsciiCheck: + + Debug.Assert(!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)); // this should have been handled earlier + + // Next, try stripping off ASCII bytes one at a time. + // We only handle up to three ASCII bytes here since we handled the four ASCII byte case above. + + if (UInt32FirstByteIsAscii(thisDWord)) + { + if (outputCharsRemaining >= 3) + { + // Fast-track: we don't need to check the destination length for subsequent + // ASCII bytes since we know we can write them all now. + + uint thisDWordLittleEndian = ToLittleEndian(thisDWord); + + nuint adjustment = 1; + pOutputBuffer[0] = (char)(byte)thisDWordLittleEndian; + + if (UInt32SecondByteIsAscii(thisDWord)) + { + adjustment++; + thisDWordLittleEndian >>= 8; + pOutputBuffer[1] = (char)(byte)thisDWordLittleEndian; + + if (UInt32ThirdByteIsAscii(thisDWord)) + { + adjustment++; + thisDWordLittleEndian >>= 8; + pOutputBuffer[2] = (char)(byte)thisDWordLittleEndian; + } + } + + pInputBuffer += adjustment; + pOutputBuffer += adjustment; + outputCharsRemaining -= (int)adjustment; + } + else + { + // Slow-track: we need to make sure each individual write has enough + // of a buffer so that we don't overrun the destination. + + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; + } + + uint thisDWordLittleEndian = ToLittleEndian(thisDWord); + + pInputBuffer++; + *pOutputBuffer++ = (char)(byte)thisDWordLittleEndian; + outputCharsRemaining--; + + if (UInt32SecondByteIsAscii(thisDWord)) + { + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; + } + + pInputBuffer++; + thisDWordLittleEndian >>= 8; + *pOutputBuffer++ = (char)(byte)thisDWordLittleEndian; + + // We can perform a small optimization here. We know at this point that + // the output buffer is fully consumed (we read two ASCII bytes and wrote + // two ASCII chars, and we checked earlier that the destination buffer + // can't store a third byte). If the next byte is ASCII, we can jump straight + // to the return statement since the end-of-method logic only relies on the + // destination buffer pointer -- NOT the output chars remaining count -- being + // correct. If the next byte is not ASCII, we'll need to continue with the + // rest of the main loop, but we can set the buffer length directly to zero + // rather than decrementing it from 1 to 0. + + Debug.Assert(outputCharsRemaining == 1); + + if (UInt32ThirdByteIsAscii(thisDWord)) + { + goto OutputBufferTooSmall; + } + else + { + outputCharsRemaining = 0; + } + } + } + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessRemainingBytesSlow; // input buffer doesn't contain enough data to read a DWORD + } + else + { + // The input buffer at the current offset contains a non-ASCII byte. + // Read an entire DWORD and fall through to multi-byte consumption logic. + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + } + } + + BeforeProcessTwoByteSequence: + + // At this point, we know we're working with a multi-byte code unit, + // but we haven't yet validated it. + + // The masks and comparands are derived from the Unicode Standard, Table 3-6. + // Additionally, we need to check for valid byte sequences per Table 3-7. + + // Check the 2-byte case. + + if (UInt32BeginsWithUtf8TwoByteMask(thisDWord)) + { + // Per Table 3-7, valid sequences are: + // [ C2..DF ] [ 80..BF ] + + if (UInt32BeginsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + goto Error; + } + + ProcessTwoByteSequenceSkipOverlongFormCheck: + + // Optimization: If this is a two-byte-per-character language like Cyrillic or Hebrew, + // there's a good chance that if we see one two-byte run then there's another two-byte + // run immediately after. Let's check that now. + + // On little-endian platforms, we can check for the two-byte UTF8 mask *and* validate that + // the value isn't overlong using a single comparison. On big-endian platforms, we'll need + // to validate the mask and validate that the sequence isn't overlong as two separate comparisons. + + if ((BitConverter.IsLittleEndian && UInt32EndsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) + || (!BitConverter.IsLittleEndian && (UInt32EndsWithUtf8TwoByteMask(thisDWord) && !UInt32EndsWithOverlongUtf8TwoByteSequence(thisDWord)))) + { + // We have two runs of two bytes each. + + if (outputCharsRemaining < 2) + { + goto ProcessRemainingBytesSlow; // running out of output buffer + } + + Unsafe.WriteUnaligned(pOutputBuffer, ExtractTwoCharsPackedFromTwoAdjacentTwoByteSequences(thisDWord)); + + pInputBuffer += 4; + pOutputBuffer += 2; + outputCharsRemaining -= 2; + + if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + // Optimization: If we read a long run of two-byte sequences, the next sequence is probably + // also two bytes. Check for that first before going back to the beginning of the loop. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (BitConverter.IsLittleEndian) + { + if (UInt32BeginsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) + { + // The next sequence is a valid two-byte sequence. + goto ProcessTwoByteSequenceSkipOverlongFormCheck; + } + } + else + { + if (UInt32BeginsWithUtf8TwoByteMask(thisDWord)) + { + if (UInt32BeginsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + goto Error; // The next sequence purports to be a 2-byte sequence but is overlong. + } + + goto ProcessTwoByteSequenceSkipOverlongFormCheck; + } + } + + // If we reached this point, the next sequence is something other than a valid + // two-byte sequence, so go back to the beginning of the loop. + goto AfterReadDWord; + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + } + + // The buffer contains a 2-byte sequence followed by 2 bytes that aren't a 2-byte sequence. + // Unlikely that a 3-byte sequence would follow a 2-byte sequence, so perhaps remaining + // bytes are ASCII? + + uint charToWrite = ExtractCharFromFirstTwoByteSequence(thisDWord); // optimistically compute this now, but don't store until we know dest is large enough + + if (UInt32ThirdByteIsAscii(thisDWord)) + { + if (UInt32FourthByteIsAscii(thisDWord)) + { + if (outputCharsRemaining < 3) + { + goto ProcessRemainingBytesSlow; // running out of output buffer + } + + pOutputBuffer[0] = (char)charToWrite; + if (BitConverter.IsLittleEndian) + { + thisDWord >>= 16; + pOutputBuffer[1] = (char)(byte)thisDWord; + thisDWord >>= 8; + pOutputBuffer[2] = (char)thisDWord; + } + else + { + pOutputBuffer[2] = (char)(byte)thisDWord; + pOutputBuffer[1] = (char)(byte)(thisDWord >> 8); + } + pInputBuffer += 4; + pOutputBuffer += 3; + outputCharsRemaining -= 3; + + continue; // go back to original bounds check and check for ASCII + } + else + { + if (outputCharsRemaining < 2) + { + goto ProcessRemainingBytesSlow; // running out of output buffer + } + + pOutputBuffer[0] = (char)charToWrite; + pOutputBuffer[1] = (char)(byte)(thisDWord >> (BitConverter.IsLittleEndian ? 16 : 8)); + pInputBuffer += 3; + pOutputBuffer += 2; + outputCharsRemaining -= 2; + + // A two-byte sequence followed by an ASCII byte followed by a non-ASCII byte. + // Read in the next DWORD and jump directly to the start of the multi-byte processing block. + + if (pFinalPosWhereCanReadDWordFromInputBuffer < pInputBuffer) + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + goto BeforeProcessTwoByteSequence; + } + } + } + else + { + if (outputCharsRemaining == 0) + { + goto ProcessRemainingBytesSlow; // running out of output buffer + } + + pOutputBuffer[0] = (char)charToWrite; + pInputBuffer += 2; + pOutputBuffer += 1; + outputCharsRemaining--; + + if (pFinalPosWhereCanReadDWordFromInputBuffer < pInputBuffer) + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + goto BeforeProcessThreeByteSequence; // we know the next byte isn't ASCII, and it's not the start of a 2-byte sequence (this was checked above) + } + } + } + + // Check the 3-byte case. + + BeforeProcessThreeByteSequence: + + if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) + { + ProcessThreeByteSequenceWithCheck: + + // We need to check for overlong or surrogate three-byte sequences. + // + // Per Table 3-7, valid sequences are: + // [ E0 ] [ A0..BF ] [ 80..BF ] + // [ E1..EC ] [ 80..BF ] [ 80..BF ] + // [ ED ] [ 80..9F ] [ 80..BF ] + // [ EE..EF ] [ 80..BF ] [ 80..BF ] + // + // Big-endian examples of using the above validation table: + // E0A0 = 1110 0000 1010 0000 => invalid (overlong ) patterns are 1110 0000 100# #### + // ED9F = 1110 1101 1001 1111 => invalid (surrogate) patterns are 1110 1101 101# #### + // If using the bitmask ......................................... 0000 1111 0010 0000 (=0F20), + // Then invalid (overlong) patterns match the comparand ......... 0000 0000 0000 0000 (=0000), + // And invalid (surrogate) patterns match the comparand ......... 0000 1101 0010 0000 (=0D20). + + if (BitConverter.IsLittleEndian) + { + // The "overlong or surrogate" check can be implemented using a single jump, but there's + // some overhead to moving the bits into the correct locations in order to perform the + // correct comparison, and in practice the processor's branch prediction capability is + // good enough that we shouldn't bother. So we'll use two jumps instead. + + // Can't extract this check into its own helper method because JITter produces suboptimal + // assembly, even with aggressive inlining. + + // Code below becomes 5 instructions: test, jz, lea, test, jz + + if (((thisDWord & 0x0000_200Fu) == 0) || (((thisDWord - 0x0000_200Du) & 0x0000_200Fu) == 0)) + { + goto Error; // overlong or surrogate + } + } + else + { + if (((thisDWord & 0x0F20_0000u) == 0) || (((thisDWord - 0x0D20_0000u) & 0x0F20_0000u) == 0)) + { + goto Error; // overlong or surrogate + } + } + + // At this point, we know the incoming scalar is well-formed. + + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; // not enough space in the destination buffer to write + } + + // As an optimization, on compatible platforms check if a second three-byte sequence immediately + // follows the one we just read, and if so use BSWAP and BMI2 to extract them together. + + if (Bmi2.X64.IsSupported) + { + Debug.Assert(BitConverter.IsLittleEndian, "BMI2 requires little-endian."); + + // First, check that the leftover byte from the original DWORD is in the range [ E0..EF ], which + // would indicate the potential start of a second three-byte sequence. + + if (((thisDWord - 0xE000_0000u) & 0xF000_0000u) == 0) + { + // The const '3' below is correct because pFinalPosWhereCanReadDWordFromInputBuffer represents + // the final place where we can safely perform a DWORD read, and we want to probe whether it's + // safe to read a DWORD beginning at address &pInputBuffer[3]. + + if (outputCharsRemaining > 1 && (nint)(void*)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) >= 3) + { + // We're going to attempt to read a second 3-byte sequence and write them both out simultaneously using PEXT. + // We need to check the continuation bit mask on the remaining two bytes (and we may as well check the leading + // byte mask again since it's free), then perform overlong + surrogate checks. If the overlong or surrogate + // checks fail, we'll fall through to the remainder of the logic which will transcode the original valid + // 3-byte UTF-8 sequence we read; and on the next iteration of the loop the validation routine will run again, + // fail, and redirect control flow to the error handling logic at the very end of this method. + + uint secondDWord = Unsafe.ReadUnaligned(pInputBuffer + 3); + + if (UInt32BeginsWithUtf8ThreeByteMask(secondDWord) + && ((secondDWord & 0x0000_200Fu) != 0) + && (((secondDWord - 0x0000_200Du) & 0x0000_200Fu) != 0)) + { + // combinedQWord = [ 1110ZZZZ 10YYYYYY 10XXXXXX ######## | 1110zzzz 10yyyyyy 10xxxxxx ######## ], where xyz are from first DWORD, XYZ are from second DWORD + ulong combinedQWord = ((ulong)BinaryPrimitives.ReverseEndianness(secondDWord) << 32) | BinaryPrimitives.ReverseEndianness(thisDWord); + thisDWord = secondDWord; // store this value in the correct local for the ASCII drain logic + + // extractedQWord = [ 00000000 00000000 00000000 00000000 | ZZZZYYYYYYXXXXXX zzzzyyyyyyxxxxxx ] + ulong extractedQWord = Bmi2.X64.ParallelBitExtract(combinedQWord, 0x0F3F3F00_0F3F3F00ul); + + Unsafe.WriteUnaligned(pOutputBuffer, (uint)extractedQWord); + pInputBuffer += 6; + pOutputBuffer += 2; + outputCharsRemaining -= 2; + + // Drain any ASCII data following the second three-byte sequence. + + goto CheckForAsciiByteAfterThreeByteSequence; + } + } + } + } + + // Couldn't extract 2x three-byte sequences together, just do this one by itself. + + *pOutputBuffer = (char)ExtractCharFromFirstThreeByteSequence(thisDWord); + pInputBuffer += 3; + pOutputBuffer += 1; + outputCharsRemaining -= 1; + + CheckForAsciiByteAfterThreeByteSequence: + + // Occasionally one-off ASCII characters like spaces, periods, or newlines will make their way + // in to the text. If this happens strip it off now before seeing if the next character + // consists of three code units. + + if (UInt32FourthByteIsAscii(thisDWord)) + { + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; + } + + if (BitConverter.IsLittleEndian) + { + *pOutputBuffer = (char)(thisDWord >> 24); + } + else + { + *pOutputBuffer = (char)(byte)thisDWord; + } + + pInputBuffer += 1; + pOutputBuffer += 1; + outputCharsRemaining -= 1; + } + + if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + // Optimization: A three-byte character could indicate CJK text, which makes it likely + // that the character following this one is also CJK. We'll check for a three-byte sequence + // marker now and jump directly to three-byte sequence processing if we see one, skipping + // all of the logic at the beginning of the loop. + + if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) + { + goto ProcessThreeByteSequenceWithCheck; // found a three-byte sequence marker; validate and consume + } + else + { + goto AfterReadDWord; // probably ASCII punctuation or whitespace + } + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + } + + // Assume the 4-byte case, but we need to validate. + + { + // We need to check for overlong or invalid (over U+10FFFF) four-byte sequences. + // + // Per Table 3-7, valid sequences are: + // [ F0 ] [ 90..BF ] [ 80..BF ] [ 80..BF ] + // [ F1..F3 ] [ 80..BF ] [ 80..BF ] [ 80..BF ] + // [ F4 ] [ 80..8F ] [ 80..BF ] [ 80..BF ] + + if (!UInt32BeginsWithUtf8FourByteMask(thisDWord)) + { + goto Error; + } + + // Now check for overlong / out-of-range sequences. + + if (BitConverter.IsLittleEndian) + { + // The DWORD we read is [ 10xxxxxx 10yyyyyy 10zzzzzz 11110www ]. + // We want to get the 'w' byte in front of the 'z' byte so that we can perform + // a single range comparison. We'll take advantage of the fact that the JITter + // can detect a ROR / ROL operation, then we'll just zero out the bytes that + // aren't involved in the range check. + + uint toCheck = thisDWord & 0x0000_FFFFu; + + // At this point, toCheck = [ 00000000 00000000 10zzzzzz 11110www ]. + + toCheck = BitOperations.RotateRight(toCheck, 8); + + // At this point, toCheck = [ 11110www 00000000 00000000 10zzzzzz ]. + + if (!UnicodeUtility.IsInRangeInclusive(toCheck, 0xF000_0090u, 0xF400_008Fu)) + { + goto Error; + } + } + else + { + if (!UnicodeUtility.IsInRangeInclusive(thisDWord, 0xF090_0000u, 0xF48F_FFFFu)) + { + goto Error; + } + } + + // Validation complete. + + if (outputCharsRemaining < 2) + { + // There's no point to falling back to the "drain the input buffer" logic, since we know + // we can't write anything to the destination. So we'll just exit immediately. + goto OutputBufferTooSmall; + } + + Unsafe.WriteUnaligned(pOutputBuffer, ExtractCharsFromFourByteSequence(thisDWord)); + + pInputBuffer += 4; + pOutputBuffer += 2; + outputCharsRemaining -= 2; + + continue; // go back to beginning of loop for processing + } + } + + ProcessRemainingBytesSlow: + inputLength = (int)(void*)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) + 4; + + ProcessInputOfLessThanDWordSize: + while (inputLength > 0) + { + uint firstByte = pInputBuffer[0]; + if (firstByte <= 0x7Fu) + { + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + // 1-byte (ASCII) case + *pOutputBuffer = (char)firstByte; + + pInputBuffer += 1; + pOutputBuffer += 1; + inputLength -= 1; + outputCharsRemaining -= 1; + continue; + } + + // Potentially the start of a multi-byte sequence? + + firstByte -= 0xC2u; + if ((byte)firstByte <= (0xDFu - 0xC2u)) + { + // Potentially a 2-byte sequence? + if (inputLength < 2) + { + goto InputBufferTooSmall; // out of data + } + + uint secondByte = pInputBuffer[1]; + if (!IsLowByteUtf8ContinuationByte(secondByte)) + { + goto Error; // 2-byte marker not followed by continuation byte + } + + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + uint asChar = (firstByte << 6) + secondByte + ((0xC2u - 0xC0u) << 6) - 0x80u; // remove UTF-8 markers from scalar + *pOutputBuffer = (char)asChar; + + pInputBuffer += 2; + pOutputBuffer += 1; + inputLength -= 2; + outputCharsRemaining -= 1; + continue; + } + else if ((byte)firstByte <= (0xEFu - 0xC2u)) + { + // Potentially a 3-byte sequence? + if (inputLength >= 3) + { + uint secondByte = pInputBuffer[1]; + uint thirdByte = pInputBuffer[2]; + if (!IsLowByteUtf8ContinuationByte(secondByte) || !IsLowByteUtf8ContinuationByte(thirdByte)) + { + goto Error; // 3-byte marker not followed by 2 continuation bytes + } + + // To speed up the validation logic below, we're not going to remove the UTF-8 markers from the partial char just yet. + // We account for this in the comparisons below. + + uint partialChar = (firstByte << 12) + (secondByte << 6); + if (partialChar < ((0xE0u - 0xC2u) << 12) + (0xA0u << 6)) + { + goto Error; // this is an overlong encoding; fail + } + + partialChar -= ((0xEDu - 0xC2u) << 12) + (0xA0u << 6); //if partialChar = 0, we're at beginning of UTF-16 surrogate code point range + if (partialChar < (0x0800u /* number of code points in UTF-16 surrogate code point range */)) + { + goto Error; // attempted to encode a UTF-16 surrogate code point; fail + } + + if (outputCharsRemaining == 0) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + // Now restore the full scalar value. + + partialChar += thirdByte; + partialChar += 0xD800; // undo "move to beginning of UTF-16 surrogate code point range" from earlier, fold it with later adds + partialChar -= 0x80u; // remove third byte continuation marker + + *pOutputBuffer = (char)partialChar; + + pInputBuffer += 3; + pOutputBuffer += 1; + inputLength -= 3; + outputCharsRemaining -= 1; + continue; + } + else if (inputLength >= 2) + { + uint secondByte = pInputBuffer[1]; + if (!IsLowByteUtf8ContinuationByte(secondByte)) + { + goto Error; // 3-byte marker not followed by continuation byte + } + + // We can't build up the entire scalar value now, but we can check for overlong / surrogate representations + // from just the first two bytes. + + uint partialChar = (firstByte << 6) + secondByte; // don't worry about fixing up the UTF-8 markers; we'll account for it in the below comparison + if (partialChar < ((0xE0u - 0xC2u) << 6) + 0xA0u) + { + goto Error; // failed overlong check + } + if (UnicodeUtility.IsInRangeInclusive(partialChar, ((0xEDu - 0xC2u) << 6) + 0xA0u, ((0xEEu - 0xC2u) << 6) + 0x7Fu)) + { + goto Error; // failed surrogate check + } + } + + goto InputBufferTooSmall; // out of data + } + else if ((byte)firstByte <= (0xF4u - 0xC2u)) + { + // Potentially a 4-byte sequence? + + if (inputLength < 2) + { + goto InputBufferTooSmall; // ran out of data + } + + uint nextByte = pInputBuffer[1]; + if (!IsLowByteUtf8ContinuationByte(nextByte)) + { + goto Error; // 4-byte marker not followed by a continuation byte + } + + uint asPartialChar = (firstByte << 6) + nextByte; // don't worry about fixing up the UTF-8 markers; we'll account for it in the below comparison + if (!UnicodeUtility.IsInRangeInclusive(asPartialChar, ((0xF0u - 0xC2u) << 6) + 0x90u, ((0xF4u - 0xC2u) << 6) + 0x8Fu)) + { + goto Error; // failed overlong / out-of-range check + } + + if (inputLength < 3) + { + goto InputBufferTooSmall; // ran out of data + } + + if (!IsLowByteUtf8ContinuationByte(pInputBuffer[2])) + { + goto Error; // third byte in 4-byte sequence not a continuation byte + } + + if (inputLength < 4) + { + goto InputBufferTooSmall; // ran out of data + } + + if (!IsLowByteUtf8ContinuationByte(pInputBuffer[3])) + { + goto Error; // fourth byte in 4-byte sequence not a continuation byte + } + + // If we read a valid astral scalar value, the only way we could've fallen down this code path + // is that we didn't have enough output buffer to write the result. + + goto OutputBufferTooSmall; + } + else + { + goto Error; // didn't begin with [ C2 .. F4 ], so invalid multi-byte sequence header byte + } + } + + OperationStatus retVal = OperationStatus.Done; + goto ReturnCommon; + + InputBufferTooSmall: + retVal = OperationStatus.NeedMoreData; + goto ReturnCommon; + + OutputBufferTooSmall: + retVal = OperationStatus.DestinationTooSmall; + goto ReturnCommon; + + Error: + retVal = OperationStatus.InvalidData; + goto ReturnCommon; + + ReturnCommon: + pInputBufferRemaining = pInputBuffer; + pOutputBufferRemaining = pOutputBuffer; + return retVal; + } + + // On method return, pInputBufferRemaining and pOutputBufferRemaining will both point to where + // the next char would have been consumed from / the next byte would have been written to. + // inputLength in chars, outputBytesRemaining in bytes. + public static OperationStatus TranscodeToUtf8(char* pInputBuffer, int inputLength, byte* pOutputBuffer, int outputBytesRemaining, out char* pInputBufferRemaining, out byte* pOutputBufferRemaining) + { + const int CharsPerDWord = sizeof(uint) / sizeof(char); + + Debug.Assert(inputLength >= 0, "Input length must not be negative."); + Debug.Assert(pInputBuffer != null || inputLength == 0, "Input length must be zero if input buffer pointer is null."); + + Debug.Assert(outputBytesRemaining >= 0, "Destination length must not be negative."); + Debug.Assert(pOutputBuffer != null || outputBytesRemaining == 0, "Destination length must be zero if destination buffer pointer is null."); + + // First, try vectorized conversion. + + { + nuint numElementsConverted = ASCIIUtility.NarrowUtf16ToAscii(pInputBuffer, pOutputBuffer, (uint)Math.Min(inputLength, outputBytesRemaining)); + + pInputBuffer += numElementsConverted; + pOutputBuffer += numElementsConverted; + + // Quick check - did we just end up consuming the entire input buffer? + // If so, short-circuit the remainder of the method. + + if ((int)numElementsConverted == inputLength) + { + pInputBufferRemaining = pInputBuffer; + pOutputBufferRemaining = pOutputBuffer; + return OperationStatus.Done; + } + + inputLength -= (int)numElementsConverted; + outputBytesRemaining -= (int)numElementsConverted; + } + + if (inputLength < CharsPerDWord) + { + goto ProcessInputOfLessThanDWordSize; + } + + char* pFinalPosWhereCanReadDWordFromInputBuffer = pInputBuffer + (uint)inputLength - CharsPerDWord; + + // Begin the main loop. + +#if DEBUG + char* pLastBufferPosProcessed = null; // used for invariant checking in debug builds +#endif + + uint thisDWord; + + while (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + // Read 32 bits at a time. This is enough to hold any possible UTF16-encoded scalar. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + AfterReadDWord: + +#if DEBUG + Debug.Assert(pLastBufferPosProcessed < pInputBuffer, "Algorithm should've made forward progress since last read."); + pLastBufferPosProcessed = pInputBuffer; +#endif + + // First, check for the common case of all-ASCII chars. + + if (Utf16Utility.AllCharsInUInt32AreAscii(thisDWord)) + { + // We read an all-ASCII sequence (2 chars). + + if (outputBytesRemaining < 2) + { + goto ProcessOneCharFromCurrentDWordAndFinish; // running out of space, but may be able to write some data + } + + // The high WORD of the local declared below might be populated with garbage + // as a result of our shifts below, but that's ok since we're only going to + // write the low WORD. + // + // [ 00000000 0bbbbbbb | 00000000 0aaaaaaa ] -> [ 00000000 0bbbbbbb | 0bbbbbbb 0aaaaaaa ] + // (Same logic works regardless of endianness.) + uint valueToWrite = thisDWord | (thisDWord >> 8); + + Unsafe.WriteUnaligned(pOutputBuffer, (ushort)valueToWrite); + + pInputBuffer += 2; + pOutputBuffer += 2; + outputBytesRemaining -= 2; + + // If we saw a sequence of all ASCII, there's a good chance a significant amount of following data is also ASCII. + // Below is basically unrolled loops with poor man's vectorization. + + uint inputCharsRemaining = (uint)(pFinalPosWhereCanReadDWordFromInputBuffer - pInputBuffer) + 2; + uint minElementsRemaining = (uint)Math.Min(inputCharsRemaining, outputBytesRemaining); + + if (Bmi2.X64.IsSupported) + { + Debug.Assert(BitConverter.IsLittleEndian, "BMI2 requires little-endian."); + const ulong PEXT_MASK = 0x00FF00FF_00FF00FFul; + + // Try reading and writing 8 elements per iteration. + uint maxIters = minElementsRemaining / 8; + ulong firstQWord, secondQWord; + int i; + for (i = 0; (uint)i < maxIters; i++) + { + firstQWord = Unsafe.ReadUnaligned(pInputBuffer); + secondQWord = Unsafe.ReadUnaligned(pInputBuffer + 4); + + if (!Utf16Utility.AllCharsInUInt64AreAscii(firstQWord | secondQWord)) + { + goto LoopTerminatedDueToNonAsciiData; + } + + Unsafe.WriteUnaligned(pOutputBuffer, (uint)Bmi2.X64.ParallelBitExtract(firstQWord, PEXT_MASK)); + Unsafe.WriteUnaligned(pOutputBuffer + 4, (uint)Bmi2.X64.ParallelBitExtract(secondQWord, PEXT_MASK)); + + pInputBuffer += 8; + pOutputBuffer += 8; + } + + outputBytesRemaining -= 8 * i; + + // Can we perform one more iteration, but reading & writing 4 elements instead of 8? + + if ((minElementsRemaining & 4) != 0) + { + secondQWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (!Utf16Utility.AllCharsInUInt64AreAscii(secondQWord)) + { + goto LoopTerminatedDueToNonAsciiDataInSecondQWord; + } + + Unsafe.WriteUnaligned(pOutputBuffer, (uint)Bmi2.X64.ParallelBitExtract(secondQWord, PEXT_MASK)); + + pInputBuffer += 4; + pOutputBuffer += 4; + outputBytesRemaining -= 4; + } + + continue; // Go back to beginning of main loop, read data, check for ASCII + + LoopTerminatedDueToNonAsciiData: + + outputBytesRemaining -= 8 * i; + + // First, see if we can drain any ASCII data from the first QWORD. + + if (Utf16Utility.AllCharsInUInt64AreAscii(firstQWord)) + { + Unsafe.WriteUnaligned(pOutputBuffer, (uint)Bmi2.X64.ParallelBitExtract(firstQWord, PEXT_MASK)); + pInputBuffer += 4; + pOutputBuffer += 4; + outputBytesRemaining -= 4; + } + else + { + secondQWord = firstQWord; + } + + LoopTerminatedDueToNonAsciiDataInSecondQWord: + + Debug.Assert(!Utf16Utility.AllCharsInUInt64AreAscii(secondQWord)); // this condition should've been checked earlier + + thisDWord = (uint)secondQWord; + if (Utf16Utility.AllCharsInUInt32AreAscii(thisDWord)) + { + // [ 00000000 0bbbbbbb | 00000000 0aaaaaaa ] -> [ 00000000 0bbbbbbb | 0bbbbbbb 0aaaaaaa ] + Unsafe.WriteUnaligned(pOutputBuffer, (ushort)(thisDWord | (thisDWord >> 8))); + pInputBuffer += 2; + pOutputBuffer += 2; + outputBytesRemaining -= 2; + thisDWord = (uint)(secondQWord >> 32); + } + + goto AfterReadDWordSkipAllCharsAsciiCheck; + } + else + { + // Can't use BMI2 x64, so we'll only read and write 4 elements per iteration. + uint maxIters = minElementsRemaining / 4; + uint secondDWord; + int i; + for (i = 0; (uint)i < maxIters; i++) + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + secondDWord = Unsafe.ReadUnaligned(pInputBuffer + 2); + + if (!Utf16Utility.AllCharsInUInt32AreAscii(thisDWord | secondDWord)) + { + goto LoopTerminatedDueToNonAsciiData; + } + + // [ 00000000 0bbbbbbb | 00000000 0aaaaaaa ] -> [ 00000000 0bbbbbbb | 0bbbbbbb 0aaaaaaa ] + // (Same logic works regardless of endianness.) + Unsafe.WriteUnaligned(pOutputBuffer, (ushort)(thisDWord | (thisDWord >> 8))); + Unsafe.WriteUnaligned(pOutputBuffer + 2, (ushort)(secondDWord | (secondDWord >> 8))); + + pInputBuffer += 4; + pOutputBuffer += 4; + } + + outputBytesRemaining -= 4 * i; + + continue; // Go back to beginning of main loop, read data, check for ASCII + + LoopTerminatedDueToNonAsciiData: + + outputBytesRemaining -= 4 * i; + + // First, see if we can drain any ASCII data from the first DWORD. + + if (Utf16Utility.AllCharsInUInt32AreAscii(thisDWord)) + { + // [ 00000000 0bbbbbbb | 00000000 0aaaaaaa ] -> [ 00000000 0bbbbbbb | 0bbbbbbb 0aaaaaaa ] + // (Same logic works regardless of endianness.) + Unsafe.WriteUnaligned(pOutputBuffer, (ushort)(thisDWord | (thisDWord >> 8))); + pInputBuffer += 2; + pOutputBuffer += 2; + outputBytesRemaining -= 2; + thisDWord = secondDWord; + } + + goto AfterReadDWordSkipAllCharsAsciiCheck; + } + } + + AfterReadDWordSkipAllCharsAsciiCheck: + + Debug.Assert(!Utf16Utility.AllCharsInUInt32AreAscii(thisDWord)); // this should have been handled earlier + + // Next, try stripping off the first ASCII char if it exists. + // We don't check for a second ASCII char since that should have been handled above. + + if (IsFirstCharAscii(thisDWord)) + { + if (outputBytesRemaining == 0) + { + goto OutputBufferTooSmall; + } + + if (BitConverter.IsLittleEndian) + { + pOutputBuffer[0] = (byte)thisDWord; // extract [ ## ## 00 AA ] + } + else + { + pOutputBuffer[0] = (byte)(thisDWord >> 24); // extract [ AA 00 ## ## ] + } + + pInputBuffer += 1; + pOutputBuffer += 1; + outputBytesRemaining -= 1; + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // input buffer doesn't contain enough data to read a DWORD + } + else + { + // The input buffer at the current offset contains a non-ASCII char. + // Read an entire DWORD and fall through to non-ASCII consumption logic. + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + } + } + + // At this point, we know the first char in the buffer is non-ASCII, but we haven't yet validated it. + + if (!IsFirstCharAtLeastThreeUtf8Bytes(thisDWord)) + { + TryConsumeMultipleTwoByteSequences: + + // For certain text (Greek, Cyrillic, ...), 2-byte sequences tend to be clustered. We'll try transcoding them in + // a tight loop without falling back to the main loop. + + if (IsSecondCharTwoUtf8Bytes(thisDWord)) + { + // We have two runs of two bytes each. + + if (outputBytesRemaining < 4) + { + goto ProcessOneCharFromCurrentDWordAndFinish; // running out of output buffer + } + + Unsafe.WriteUnaligned(pOutputBuffer, ExtractTwoUtf8TwoByteSequencesFromTwoPackedUtf16Chars(thisDWord)); + + pInputBuffer += 2; + pOutputBuffer += 4; + outputBytesRemaining -= 4; + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // Running out of data - go down slow path + } + else + { + // Optimization: If we read a long run of two-byte sequences, the next sequence is probably + // also two bytes. Check for that first before going back to the beginning of the loop. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (IsFirstCharTwoUtf8Bytes(thisDWord)) + { + // Validated we have a two-byte sequence coming up + goto TryConsumeMultipleTwoByteSequences; + } + + // If we reached this point, the next sequence is something other than a valid + // two-byte sequence, so go back to the beginning of the loop. + goto AfterReadDWord; + } + } + + if (outputBytesRemaining < 2) + { + goto OutputBufferTooSmall; + } + + Unsafe.WriteUnaligned(pOutputBuffer, (ushort)ExtractUtf8TwoByteSequenceFromFirstUtf16Char(thisDWord)); + + // The buffer contains a 2-byte sequence followed by 2 bytes that aren't a 2-byte sequence. + // Unlikely that a 3-byte sequence would follow a 2-byte sequence, so perhaps remaining + // char is ASCII? + + if (IsSecondCharAscii(thisDWord)) + { + if (outputBytesRemaining >= 3) + { + if (BitConverter.IsLittleEndian) + { + thisDWord >>= 16; + } + pOutputBuffer[2] = (byte)thisDWord; + + pInputBuffer += 2; + pOutputBuffer += 3; + outputBytesRemaining -= 3; + + continue; // go back to original bounds check and check for ASCII + } + else + { + pInputBuffer += 1; + pOutputBuffer += 2; + goto OutputBufferTooSmall; + } + } + else + { + pInputBuffer += 1; + pOutputBuffer += 2; + outputBytesRemaining -= 2; + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + goto BeforeProcessThreeByteSequence; // we know the next byte isn't ASCII, and it's not the start of a 2-byte sequence (this was checked above) + } + } + } + + // Check the 3-byte case. + + BeforeProcessThreeByteSequence: + + if (!IsFirstCharSurrogate(thisDWord)) + { + // Optimization: A three-byte character could indicate CJK text, which makes it likely + // that the character following this one is also CJK. We'll perform the check now + // rather than jumping to the beginning of the main loop. + + if (IsSecondCharAtLeastThreeUtf8Bytes(thisDWord)) + { + if (!IsSecondCharSurrogate(thisDWord)) + { + if (outputBytesRemaining < 6) + { + goto ConsumeSingleThreeByteRun; // not enough space - try consuming as much as we can + } + + WriteTwoUtf16CharsAsTwoUtf8ThreeByteSequences(ref *pOutputBuffer, thisDWord); + + pInputBuffer += 2; + pOutputBuffer += 6; + outputBytesRemaining -= 6; + + // Try to remain in the 3-byte processing loop if at all possible. + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (IsFirstCharAtLeastThreeUtf8Bytes(thisDWord)) + { + goto BeforeProcessThreeByteSequence; + } + else + { + // Fall back to standard processing loop since we don't know how to optimize this. + goto AfterReadDWord; + } + } + } + } + + ConsumeSingleThreeByteRun: + + if (outputBytesRemaining < 3) + { + goto OutputBufferTooSmall; + } + + WriteFirstUtf16CharAsUtf8ThreeByteSequence(ref *pOutputBuffer, thisDWord); + + pInputBuffer += 1; + pOutputBuffer += 3; + outputBytesRemaining -= 3; + + // Occasionally one-off ASCII characters like spaces, periods, or newlines will make their way + // in to the text. If this happens strip it off now before seeing if the next character + // consists of three code units. + + if (IsSecondCharAscii(thisDWord)) + { + if (outputBytesRemaining == 0) + { + goto OutputBufferTooSmall; + } + + if (BitConverter.IsLittleEndian) + { + *pOutputBuffer = (byte)(thisDWord >> 16); + } + else + { + *pOutputBuffer = (byte)(thisDWord); + } + + pInputBuffer += 1; + pOutputBuffer += 1; + outputBytesRemaining -= 1; + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (IsFirstCharAtLeastThreeUtf8Bytes(thisDWord)) + { + goto BeforeProcessThreeByteSequence; + } + else + { + // Fall back to standard processing loop since we don't know how to optimize this. + goto AfterReadDWord; + } + } + } + + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessNextCharAndFinish; // Running out of data - go down slow path + } + else + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + goto AfterReadDWordSkipAllCharsAsciiCheck; // we just checked above that this value isn't ASCII + } + } + + // Four byte sequence processing + + if (IsWellFormedUtf16SurrogatePair(thisDWord)) + { + if (outputBytesRemaining < 4) + { + goto OutputBufferTooSmall; + } + + Unsafe.WriteUnaligned(pOutputBuffer, ExtractFourUtf8BytesFromSurrogatePair(thisDWord)); + + pInputBuffer += 2; + pOutputBuffer += 4; + outputBytesRemaining -= 4; + + continue; // go back to beginning of loop for processing + } + + goto Error; // an ill-formed surrogate sequence: high not followed by low, or low not preceded by high + } + + ProcessNextCharAndFinish: + inputLength = (int)(pFinalPosWhereCanReadDWordFromInputBuffer - pInputBuffer) + CharsPerDWord; + + ProcessInputOfLessThanDWordSize: + Debug.Assert(inputLength < CharsPerDWord); + + if (inputLength == 0) + { + goto InputBufferFullyConsumed; + } + + uint thisChar = *pInputBuffer; + goto ProcessFinalChar; + + ProcessOneCharFromCurrentDWordAndFinish: + if (BitConverter.IsLittleEndian) + { + thisChar = thisDWord & 0xFFFFu; // preserve only the first char + } + else + { + thisChar = thisDWord >> 16; // preserve only the first char + } + + ProcessFinalChar: + { + if (thisChar <= 0x7Fu) + { + if (outputBytesRemaining == 0) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + // 1-byte (ASCII) case + *pOutputBuffer = (byte)thisChar; + + pInputBuffer += 1; + pOutputBuffer += 1; + } + else if (thisChar < 0x0800u) + { + if (outputBytesRemaining < 2) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + // 2-byte case + pOutputBuffer[1] = (byte)((thisChar & 0x3Fu) | unchecked((uint)(sbyte)0x80)); // [ 10xxxxxx ] + pOutputBuffer[0] = (byte)((thisChar >> 6) | unchecked((uint)(sbyte)0xC0)); // [ 110yyyyy ] + + pInputBuffer += 1; + pOutputBuffer += 2; + } + else if (!UnicodeUtility.IsSurrogateCodePoint(thisChar)) + { + if (outputBytesRemaining < 3) + { + goto OutputBufferTooSmall; // we have no hope of writing anything to the output + } + + // 3-byte case + pOutputBuffer[2] = (byte)((thisChar & 0x3Fu) | unchecked((uint)(sbyte)0x80)); // [ 10xxxxxx ] + pOutputBuffer[1] = (byte)(((thisChar >> 6) & 0x3Fu) | unchecked((uint)(sbyte)0x80)); // [ 10yyyyyy ] + pOutputBuffer[0] = (byte)((thisChar >> 12) | unchecked((uint)(sbyte)0xE0)); // [ 1110zzzz ] + + pInputBuffer += 1; + pOutputBuffer += 3; + } + else if (thisChar <= 0xDBFFu) + { + // UTF-16 high surrogate code point with no trailing data, report incomplete input buffer + goto InputBufferTooSmall; + } + else + { + // UTF-16 low surrogate code point with no leading data, report error + goto Error; + } + } + + // There are two ways we can end up here. Either we were running low on input data, + // or we were running low on space in the destination buffer. If we're running low on + // input data (label targets ProcessInputOfLessThanDWordSize and ProcessNextCharAndFinish), + // then the inputLength value is guaranteed to be between 0 and 1, and we should return Done. + // If we're running low on destination buffer space (label target ProcessOneCharFromCurrentDWordAndFinish), + // then we didn't modify inputLength since entering the main loop, which means it should + // still have a value of >= 2. So checking the value of inputLength is all we need to do to determine + // which of the two scenarios we're in. + + if (inputLength > 1) + { + goto OutputBufferTooSmall; + } + + InputBufferFullyConsumed: + OperationStatus retVal = OperationStatus.Done; + goto ReturnCommon; + + InputBufferTooSmall: + retVal = OperationStatus.NeedMoreData; + goto ReturnCommon; + + OutputBufferTooSmall: + retVal = OperationStatus.DestinationTooSmall; + goto ReturnCommon; + + Error: + retVal = OperationStatus.InvalidData; + goto ReturnCommon; + + ReturnCommon: + pInputBufferRemaining = pInputBuffer; + pOutputBufferRemaining = pOutputBuffer; + return retVal; + } + } +} diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Validation.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Validation.cs new file mode 100644 index 000000000000..6425ae1da30d --- /dev/null +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.Validation.cs @@ -0,0 +1,737 @@ +// 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.Diagnostics; +using System.Numerics; +using System.Runtime.Intrinsics.X86; +using Internal.Runtime.CompilerServices; + +#if BIT64 +using nint = System.Int64; +using nuint = System.UInt64; +#else // BIT64 +using nint = System.Int32; +using nuint = System.UInt32; +#endif // BIT64 + +namespace System.Text.Unicode +{ + internal static unsafe partial class Utf8Utility + { +#if DEBUG + private static void _ValidateAdditionalNIntDefinitions() + { + Debug.Assert(sizeof(nint) == IntPtr.Size && nint.MinValue < 0, "nint is defined incorrectly."); + Debug.Assert(sizeof(nuint) == IntPtr.Size && nuint.MinValue == 0, "nuint is defined incorrectly."); + } +#endif // DEBUG + + // Returns &inputBuffer[inputLength] if the input buffer is valid. + /// + /// Given an input buffer of byte length , + /// returns a pointer to where the first invalid data appears in . + /// + /// + /// Returns a pointer to the end of if the buffer is well-formed. + /// + public static byte* GetPointerToFirstInvalidByte(byte* pInputBuffer, int inputLength, out int utf16CodeUnitCountAdjustment, out int scalarCountAdjustment) + { + Debug.Assert(inputLength >= 0, "Input length must not be negative."); + Debug.Assert(pInputBuffer != null || inputLength == 0, "Input length must be zero if input buffer pointer is null."); + + // First, try to drain off as many ASCII bytes as we can from the beginning. + + { + nuint numAsciiBytesCounted = ASCIIUtility.GetIndexOfFirstNonAsciiByte(pInputBuffer, (uint)inputLength); + pInputBuffer += numAsciiBytesCounted; + + // Quick check - did we just end up consuming the entire input buffer? + // If so, short-circuit the remainder of the method. + + inputLength -= (int)numAsciiBytesCounted; + if (inputLength == 0) + { + utf16CodeUnitCountAdjustment = 0; + scalarCountAdjustment = 0; + return pInputBuffer; + } + } + +#if DEBUG + // Keep these around for final validation at the end of the method. + byte* pOriginalInputBuffer = pInputBuffer; + int originalInputLength = inputLength; +#endif + + // Enregistered locals that we'll eventually out to our caller. + + int tempUtf16CodeUnitCountAdjustment = 0; + int tempScalarCountAdjustment = 0; + + if (inputLength < sizeof(uint)) + { + goto ProcessInputOfLessThanDWordSize; + } + + byte* pFinalPosWhereCanReadDWordFromInputBuffer = pInputBuffer + (uint)inputLength - sizeof(uint); + + // Begin the main loop. + +#if DEBUG + byte* pLastBufferPosProcessed = null; // used for invariant checking in debug builds +#endif + + while (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + // Read 32 bits at a time. This is enough to hold any possible UTF8-encoded scalar. + + uint thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + AfterReadDWord: + +#if DEBUG + Debug.Assert(pLastBufferPosProcessed < pInputBuffer, "Algorithm should've made forward progress since last read."); + pLastBufferPosProcessed = pInputBuffer; +#endif + + // First, check for the common case of all-ASCII bytes. + + if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) + { + // We read an all-ASCII sequence. + + pInputBuffer += sizeof(uint); + + // If we saw a sequence of all ASCII, there's a good chance a significant amount of following data is also ASCII. + // Below is basically unrolled loops with poor man's vectorization. + + // Below check is "can I read at least five DWORDs from the input stream?" + // n.b. Since we incremented pInputBuffer above the below subtraction may result in a negative value, + // hence using nint instead of nuint. + + if ((nint)(void*)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) >= 4 * sizeof(uint)) + { + // We want reads in the inner loop to be aligned. So let's perform a quick + // ASCII check of the next 32 bits (4 bytes) now, and if that succeeds bump + // the read pointer up to the next aligned address. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + if (!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) + { + goto AfterReadDWordSkipAllBytesAsciiCheck; + } + + pInputBuffer = (byte*)((nuint)(pInputBuffer + 4) & ~(nuint)3); + + // At this point, the input buffer offset points to an aligned DWORD. We also know that there's + // enough room to read at least four DWORDs from the buffer. (Heed the comment a few lines above: + // the original 'if' check confirmed that there were 5 DWORDs before the alignment check, and + // the alignment check consumes at most a single DWORD.) + + byte* pInputBufferFinalPosAtWhichCanSafelyLoop = pFinalPosWhereCanReadDWordFromInputBuffer - 3 * sizeof(uint); // can safely read 4 DWORDs here + uint mask; + + do + { + if (Sse2.IsSupported && Bmi1.IsSupported) + { + // pInputBuffer is 32-bit aligned but not necessary 128-bit aligned, so we're + // going to perform an unaligned load. We don't necessarily care about aligning + // this because we pessimistically assume we'll encounter non-ASCII data at some + // point in the not-too-distant future (otherwise we would've stayed entirely + // within the all-ASCII vectorized code at the entry to this method). + + mask = (uint)Sse2.MoveMask(Sse2.LoadVector128((byte*)pInputBuffer)); + if (mask != 0) + { + goto Sse2LoopTerminatedEarlyDueToNonAsciiData; + } + } + else + { + if (!ASCIIUtility.AllBytesInUInt32AreAscii(((uint*)pInputBuffer)[0] | ((uint*)pInputBuffer)[1])) + { + goto LoopTerminatedEarlyDueToNonAsciiDataInFirstPair; + } + + if (!ASCIIUtility.AllBytesInUInt32AreAscii(((uint*)pInputBuffer)[2] | ((uint*)pInputBuffer)[3])) + { + goto LoopTerminatedEarlyDueToNonAsciiDataInSecondPair; + } + } + + pInputBuffer += 4 * sizeof(uint); // consumed 4 DWORDs + } while (pInputBuffer <= pInputBufferFinalPosAtWhichCanSafelyLoop); + + continue; // need to perform a bounds check because we might be running out of data + + Sse2LoopTerminatedEarlyDueToNonAsciiData: + + Debug.Assert(BitConverter.IsLittleEndian); + Debug.Assert(Sse2.IsSupported); + Debug.Assert(Bmi1.IsSupported); + + // The 'mask' value will have a 0 bit for each ASCII byte we saw and a 1 bit + // for each non-ASCII byte we saw. We can count the number of ASCII bytes, + // bump our input counter by that amount, and resume processing from the + // "the first byte is no longer ASCII" portion of the main loop. + + Debug.Assert(mask != 0); + + pInputBuffer += Bmi1.TrailingZeroCount(mask); + if (pInputBuffer > pFinalPosWhereCanReadDWordFromInputBuffer) + { + goto ProcessRemainingBytesSlow; + } + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); // no longer guaranteed to be aligned + goto BeforeProcessTwoByteSequence; + + LoopTerminatedEarlyDueToNonAsciiDataInSecondPair: + + pInputBuffer += 2 * sizeof(uint); // consumed 2 DWORDs + + LoopTerminatedEarlyDueToNonAsciiDataInFirstPair: + + // We know that there's *at least* two DWORDs of data remaining in the buffer. + // We also know that one of them (or both of them) contains non-ASCII data somewhere. + // Let's perform a quick check here to bypass the logic at the beginning of the main loop. + + thisDWord = *(uint*)pInputBuffer; // still aligned here + if (ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)) + { + pInputBuffer += sizeof(uint); // consumed 1 more DWORD + thisDWord = *(uint*)pInputBuffer; // still aligned here + } + + goto AfterReadDWordSkipAllBytesAsciiCheck; + } + + continue; // not enough data remaining to unroll loop - go back to beginning with bounds checks + } + + AfterReadDWordSkipAllBytesAsciiCheck: + + Debug.Assert(!ASCIIUtility.AllBytesInUInt32AreAscii(thisDWord)); // this should have been handled earlier + + // Next, try stripping off ASCII bytes one at a time. + // We only handle up to three ASCII bytes here since we handled the four ASCII byte case above. + + { + uint numLeadingAsciiBytes = ASCIIUtility.CountNumberOfLeadingAsciiBytesFromUInt32WithSomeNonAsciiData(thisDWord); + pInputBuffer += numLeadingAsciiBytes; + + if (pFinalPosWhereCanReadDWordFromInputBuffer < pInputBuffer) + { + goto ProcessRemainingBytesSlow; // Input buffer doesn't contain enough data to read a DWORD + } + else + { + // The input buffer at the current offset contains a non-ASCII byte. + // Read an entire DWORD and fall through to multi-byte consumption logic. + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + } + } + + BeforeProcessTwoByteSequence: + + // At this point, we suspect we're working with a multi-byte code unit sequence, + // but we haven't yet validated it for well-formedness. + + // The masks and comparands are derived from the Unicode Standard, Table 3-6. + // Additionally, we need to check for valid byte sequences per Table 3-7. + + // Check the 2-byte case. + + thisDWord -= (BitConverter.IsLittleEndian) ? 0x0000_80C0u : 0xC080_0000u; + if ((thisDWord & (BitConverter.IsLittleEndian ? 0x0000_C0E0u : 0xE0C0_0000u)) == 0) + { + // Per Table 3-7, valid sequences are: + // [ C2..DF ] [ 80..BF ] + // + // Due to our modification of 'thisDWord' above, this becomes: + // [ 02..1F ] [ 00..3F ] + // + // We've already checked that the leading byte was originally in the range [ C0..DF ] + // and that the trailing byte was originally in the range [ 80..BF ], so now we only need + // to check that the modified leading byte is >= [ 02 ]. + + if ((BitConverter.IsLittleEndian && (byte)thisDWord < 0x02u) + || (!BitConverter.IsLittleEndian && thisDWord < 0x0200_0000u)) + { + goto Error; // overlong form - leading byte was [ C0 ] or [ C1 ] + } + + ProcessTwoByteSequenceSkipOverlongFormCheck: + + // Optimization: If this is a two-byte-per-character language like Cyrillic or Hebrew, + // there's a good chance that if we see one two-byte run then there's another two-byte + // run immediately after. Let's check that now. + + // On little-endian platforms, we can check for the two-byte UTF8 mask *and* validate that + // the value isn't overlong using a single comparison. On big-endian platforms, we'll need + // to validate the mask and validate that the sequence isn't overlong as two separate comparisons. + + if ((BitConverter.IsLittleEndian && UInt32EndsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) + || (!BitConverter.IsLittleEndian && (UInt32EndsWithUtf8TwoByteMask(thisDWord) && !UInt32EndsWithOverlongUtf8TwoByteSequence(thisDWord)))) + { + // We have two runs of two bytes each. + pInputBuffer += 4; + tempUtf16CodeUnitCountAdjustment -= 2; // 4 UTF-8 code units -> 2 UTF-16 code units (and 2 scalars) + + if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + // Optimization: If we read a long run of two-byte sequences, the next sequence is probably + // also two bytes. Check for that first before going back to the beginning of the loop. + + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + if (BitConverter.IsLittleEndian) + { + if (UInt32BeginsWithValidUtf8TwoByteSequenceLittleEndian(thisDWord)) + { + // The next sequence is a valid two-byte sequence. + goto ProcessTwoByteSequenceSkipOverlongFormCheck; + } + } + else + { + if (UInt32BeginsWithUtf8TwoByteMask(thisDWord)) + { + if (UInt32BeginsWithOverlongUtf8TwoByteSequence(thisDWord)) + { + goto Error; // The next sequence purports to be a 2-byte sequence but is overlong. + } + + goto ProcessTwoByteSequenceSkipOverlongFormCheck; + } + } + + // If we reached this point, the next sequence is something other than a valid + // two-byte sequence, so go back to the beginning of the loop. + goto AfterReadDWord; + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data - go down slow path + } + } + + // The buffer contains a 2-byte sequence followed by 2 bytes that aren't a 2-byte sequence. + // Unlikely that a 3-byte sequence would follow a 2-byte sequence, so perhaps remaining + // bytes are ASCII? + + tempUtf16CodeUnitCountAdjustment--; // 2-byte sequence + (some number of ASCII bytes) -> 1 UTF-16 code units (and 1 scalar) [+ trailing] + + if (UInt32ThirdByteIsAscii(thisDWord)) + { + if (UInt32FourthByteIsAscii(thisDWord)) + { + pInputBuffer += 4; + } + else + { + pInputBuffer += 3; + + // A two-byte sequence followed by an ASCII byte followed by a non-ASCII byte. + // Read in the next DWORD and jump directly to the start of the multi-byte processing block. + + if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + goto BeforeProcessTwoByteSequence; + } + } + } + else + { + pInputBuffer += 2; + } + + continue; + } + + // Check the 3-byte case. + // We need to restore the C0 leading byte we stripped out earlier, then we can strip out the expected E0 byte. + + thisDWord -= (BitConverter.IsLittleEndian) ? (0x0080_00E0u - 0x0000_00C0u) : (0xE000_8000u - 0xC000_0000u); + if ((thisDWord & (BitConverter.IsLittleEndian ? 0x00C0_C0F0u : 0xF0C0_C000u)) == 0) + { + ProcessThreeByteSequenceWithCheck: + + // We assume the caller has confirmed that the bit pattern is representative of a three-byte + // sequence, but it may still be overlong or surrogate. We need to check for these possibilities. + // + // Per Table 3-7, valid sequences are: + // [ E0 ] [ A0..BF ] [ 80..BF ] + // [ E1..EC ] [ 80..BF ] [ 80..BF ] + // [ ED ] [ 80..9F ] [ 80..BF ] + // [ EE..EF ] [ 80..BF ] [ 80..BF ] + // + // Big-endian examples of using the above validation table: + // E0A0 = 1110 0000 1010 0000 => invalid (overlong ) patterns are 1110 0000 100# #### + // ED9F = 1110 1101 1001 1111 => invalid (surrogate) patterns are 1110 1101 101# #### + // If using the bitmask ......................................... 0000 1111 0010 0000 (=0F20), + // Then invalid (overlong) patterns match the comparand ......... 0000 0000 0000 0000 (=0000), + // And invalid (surrogate) patterns match the comparand ......... 0000 1101 0010 0000 (=0D20). + // + // It's ok if the caller has manipulated 'thisDWord' (e.g., by subtracting 0xE0 or 0x80) + // as long as they haven't touched the bits we're about to use in our mask checking below. + + if (BitConverter.IsLittleEndian) + { + // The "overlong or surrogate" check can be implemented using a single jump, but there's + // some overhead to moving the bits into the correct locations in order to perform the + // correct comparison, and in practice the processor's branch prediction capability is + // good enough that we shouldn't bother. So we'll use two jumps instead. + + // Can't extract this check into its own helper method because JITter produces suboptimal + // assembly, even with aggressive inlining. + + // Code below becomes 5 instructions: test, jz, add, test, jz + + if (((thisDWord & 0x0000_200Fu) == 0) || (((thisDWord -= 0x0000_200Du) & 0x0000_200Fu) == 0)) + { + goto Error; // overlong or surrogate + } + } + else + { + if (((thisDWord & 0x0F20_0000u) == 0) || (((thisDWord -= 0x0D20_0000u) & 0x0F20_0000u) == 0)) + { + goto Error; // overlong or surrogate + } + } + + ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks: + + // Occasionally one-off ASCII characters like spaces, periods, or newlines will make their way + // in to the text. If this happens strip it off now before seeing if the next character + // consists of three code units. + + // Branchless: consume a 3-byte UTF-8 sequence and optionally an extra ASCII byte hanging off the end + + nint asciiAdjustment; + if (BitConverter.IsLittleEndian) + { + asciiAdjustment = (int)thisDWord >> 31; // smear most significant bit across entire value + } + else + { + asciiAdjustment = (nint)(sbyte)thisDWord >> 7; // smear most significant bit of least significant byte across entire value + } + + // asciiAdjustment = 0 if fourth byte is ASCII; -1 otherwise + + // Please *DO NOT* reorder the below two lines. It provides extra defense in depth in case this method + // is ever changed such that pInputBuffer becomes a 'ref byte' instead of a simple 'byte*'. It's valid + // to add 4 before backing up since we already checked previously that the input buffer contains at + // least a DWORD's worth of data, so we're not going to run past the end of the buffer where the GC can + // no longer track the reference. However, we can't back up before adding 4, since we might back up to + // before the start of the buffer, and the GC isn't guaranteed to be able to track this. + + pInputBuffer += 4; // optimistically, assume consumed a 3-byte UTF-8 sequence plus an extra ASCII byte + pInputBuffer += asciiAdjustment; // back up if we didn't actually consume an ASCII byte + + tempUtf16CodeUnitCountAdjustment -= 2; // 3 (or 4) UTF-8 bytes -> 1 (or 2) UTF-16 code unit (and 1 [or 2] scalar) + + SuccessfullyProcessedThreeByteSequence: + + if (IntPtr.Size >= 8 && BitConverter.IsLittleEndian) + { + // x64 little-endian optimization: A three-byte character could indicate CJK text, + // which makes it likely that the character following this one is also CJK. + // We'll try to process several three-byte sequences at a time. + + // The check below is really "can we read 9 bytes from the input buffer?" since 'pFinalPos...' is already offset + // n.b. The subtraction below could result in a negative value (since we advanced pInputBuffer above), so + // use nint instead of nuint. + + if ((nint)(pFinalPosWhereCanReadDWordFromInputBuffer - pInputBuffer) >= 5) + { + ulong thisQWord = Unsafe.ReadUnaligned(pInputBuffer); + + // Stage the next 32 bits into 'thisDWord' so that it's ready for us in case we need to jump backward + // to a previous location in the loop. This offers defense against reading main memory again (which may + // have been modified and could lead to a race condition). + + thisDWord = (uint)thisQWord; + + // Is this three 3-byte sequences in a row? + // thisQWord = [ 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] [ 10xxxxxx ] + // ---- CHAR 3 ---- --------- CHAR 2 --------- --------- CHAR 1 --------- -CHAR 3- + if ((thisQWord & 0xC0F0_C0C0_F0C0_C0F0ul) == 0x80E0_8080_E080_80E0ul && IsUtf8ContinuationByte(in pInputBuffer[8])) + { + // Saw a proper bitmask for three incoming 3-byte sequences, perform the + // overlong and surrogate sequence checking now. + + // Check the first character. + // If the first character is overlong or a surrogate, fail immediately. + + if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) + { + goto Error; + } + + // Check the second character. + // At this point, we now know the first three bytes represent a well-formed sequence. + // If there's an error beyond here, we'll jump back to the "process three known good bytes" + // logic. + + thisQWord >>= 24; + if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) + { + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + // Check the third character (we already checked that it's followed by a continuation byte). + + thisQWord >>= 24; + if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) + { + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + pInputBuffer += 9; + tempUtf16CodeUnitCountAdjustment -= 6; // 9 UTF-8 bytes -> 3 UTF-16 code units (and 3 scalars) + + goto SuccessfullyProcessedThreeByteSequence; + } + + // Is this two 3-byte sequences in a row? + // thisQWord = [ ######## ######## | 10xxxxxx 10yyyyyy 1110zzzz | 10xxxxxx 10yyyyyy 1110zzzz ] + // --------- CHAR 2 --------- --------- CHAR 1 --------- + if ((thisQWord & 0xC0C0_F0C0_C0F0ul) == 0x8080_E080_80E0ul) + { + // Saw a proper bitmask for two incoming 3-byte sequences, perform the + // overlong and surrogate sequence checking now. + + // Check the first character. + // If the first character is overlong or a surrogate, fail immediately. + + if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) + { + goto Error; + } + + // Check the second character. + // At this point, we now know the first three bytes represent a well-formed sequence. + // If there's an error beyond here, we'll jump back to the "process three known good bytes" + // logic. + + thisQWord >>= 24; + if ((((uint)thisQWord & 0x200Fu) == 0) || ((((uint)thisQWord - 0x200Du) & 0x200Fu) == 0)) + { + goto ProcessSingleThreeByteSequenceSkipOverlongAndSurrogateChecks; + } + + pInputBuffer += 6; + tempUtf16CodeUnitCountAdjustment -= 4; // 6 UTF-8 bytes -> 2 UTF-16 code units (and 2 scalars) + + // The next byte in the sequence didn't have a 3-byte marker, so it's probably + // an ASCII character. Jump back to the beginning of loop processing. + + continue; + } + + if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) + { + // A single three-byte sequence. + goto ProcessThreeByteSequenceWithCheck; + } + else + { + // Not a three-byte sequence; perhaps ASCII? + goto AfterReadDWord; + } + } + } + + if (pInputBuffer <= pFinalPosWhereCanReadDWordFromInputBuffer) + { + thisDWord = Unsafe.ReadUnaligned(pInputBuffer); + + // Optimization: A three-byte character could indicate CJK text, which makes it likely + // that the character following this one is also CJK. We'll check for a three-byte sequence + // marker now and jump directly to three-byte sequence processing if we see one, skipping + // all of the logic at the beginning of the loop. + + if (UInt32BeginsWithUtf8ThreeByteMask(thisDWord)) + { + goto ProcessThreeByteSequenceWithCheck; // Found another [not yet validated] three-byte sequence; process + } + else + { + goto AfterReadDWord; // Probably ASCII punctuation or whitespace; go back to start of loop + } + } + else + { + goto ProcessRemainingBytesSlow; // Running out of data + } + } + + // Assume the 4-byte case, but we need to validate. + + if (BitConverter.IsLittleEndian) + { + thisDWord &= 0xC0C0_FFFFu; + + // After the above modifications earlier in this method, we expect 'thisDWord' + // to have the structure [ 10000000 00000000 00uuzzzz 00010uuu ]. We'll now + // perform two checks to confirm this. The first will verify the + // [ 10000000 00000000 00###### ######## ] structure by taking advantage of two's + // complement representation to perform a single *signed* integer check. + + if ((int)thisDWord > unchecked((int)0x8000_3FFF)) + { + goto Error; // didn't have three trailing bytes + } + + // Now we want to confirm that 0x01 <= uuuuu (otherwise this is an overlong encoding) + // and that uuuuu <= 0x10 (otherwise this is an out-of-range encoding). + + thisDWord = BitOperations.RotateRight(thisDWord, 8); + + // Now, thisDWord = [ 00010uuu 10000000 00000000 00uuzzzz ]. + // The check is now a simple add / cmp / jcc combo. + + if (!UnicodeUtility.IsInRangeInclusive(thisDWord, 0x1080_0010u, 0x1480_000Fu)) + { + goto Error; // overlong or out-of-range + } + } + else + { + thisDWord -= 0x80u; + + // After the above modifications earlier in this method, we expect 'thisDWord' + // to have the structure [ 00010uuu 00uuzzzz 00yyyyyy 00xxxxxx ]. We'll now + // perform two checks to confirm this. The first will verify the + // [ ######## 00###### 00###### 00###### ] structure. + + if ((thisDWord & 0x00C0_C0C0u) != 0) + { + goto Error; // didn't have three trailing bytes + } + + // Now we want to confirm that 0x01 <= uuuuu (otherwise this is an overlong encoding) + // and that uuuuu <= 0x10 (otherwise this is an out-of-range encoding). + // This is a simple range check. (We don't care about the low two bytes.) + + if (!UnicodeUtility.IsInRangeInclusive(thisDWord, 0x1010_0000u, 0x140F_FFFFu)) + { + goto Error; // overlong or out-of-range + } + } + + // Validation of 4-byte case complete. + + pInputBuffer += 4; + tempUtf16CodeUnitCountAdjustment -= 2; // 4 UTF-8 bytes -> 2 UTF-16 code units + tempScalarCountAdjustment--; // 2 UTF-16 code units -> 1 scalar + + continue; // go back to beginning of loop for processing + } + + goto ProcessRemainingBytesSlow; + + ProcessInputOfLessThanDWordSize: + + Debug.Assert(inputLength < 4); + nuint inputBufferRemainingBytes = (uint)inputLength; + goto ProcessSmallBufferCommon; + + ProcessRemainingBytesSlow: + + inputBufferRemainingBytes = (nuint)(void*)Unsafe.ByteOffset(ref *pInputBuffer, ref *pFinalPosWhereCanReadDWordFromInputBuffer) + 4; + + ProcessSmallBufferCommon: + + Debug.Assert(inputBufferRemainingBytes < 4); + while (inputBufferRemainingBytes > 0) + { + uint firstByte = pInputBuffer[0]; + + if ((byte)firstByte < 0x80u) + { + // 1-byte (ASCII) case + pInputBuffer++; + inputBufferRemainingBytes--; + continue; + } + else if (inputBufferRemainingBytes >= 2) + { + uint secondByte = pInputBuffer[1]; // typed as 32-bit since we perform arithmetic (not just comparisons) on this value + if ((byte)firstByte < 0xE0u) + { + // 2-byte case + if ((byte)firstByte >= 0xC2u && IsLowByteUtf8ContinuationByte(secondByte)) + { + pInputBuffer += 2; + tempUtf16CodeUnitCountAdjustment--; // 2 UTF-8 bytes -> 1 UTF-16 code unit (and 1 scalar) + inputBufferRemainingBytes -= 2; + continue; + } + } + else if (inputBufferRemainingBytes >= 3) + { + if ((byte)firstByte < 0xF0u) + { + if ((byte)firstByte == 0xE0u) + { + if (!UnicodeUtility.IsInRangeInclusive(secondByte, 0xA0u, 0xBFu)) + { + goto Error; // overlong encoding + } + } + else if ((byte)firstByte == 0xEDu) + { + if (!UnicodeUtility.IsInRangeInclusive(secondByte, 0x80u, 0x9Fu)) + { + goto Error; // would be a UTF-16 surrogate code point + } + } + else + { + if (!IsLowByteUtf8ContinuationByte(secondByte)) + { + goto Error; // first trailing byte doesn't have proper continuation marker + } + } + + if (IsUtf8ContinuationByte(in pInputBuffer[2])) + { + pInputBuffer += 3; + tempUtf16CodeUnitCountAdjustment -= 2; // 3 UTF-8 bytes -> 2 UTF-16 code units (and 2 scalars) + inputBufferRemainingBytes -= 3; + continue; + } + } + } + } + + // Error - no match. + + goto Error; + } + + // If we reached this point, we're out of data, and we saw no bad UTF8 sequence. + +#if DEBUG + // Quick check that for the success case we're going to fulfill our contract of returning &inputBuffer[inputLength]. + Debug.Assert(pOriginalInputBuffer + originalInputLength == pInputBuffer, "About to return an unexpected value."); +#endif + + Error: + + // Report back to our caller how far we got before seeing invalid data. + // (Also used for normal termination when falling out of the loop above.) + + utf16CodeUnitCountAdjustment = tempUtf16CodeUnitCountAdjustment; + scalarCountAdjustment = tempScalarCountAdjustment; + return pInputBuffer; + } + } +} diff --git a/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.cs b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.cs index 6ee9ca05a659..d24f76647499 100644 --- a/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.cs +++ b/src/Common/src/CoreLib/System/Text/Unicode/Utf8Utility.cs @@ -6,10 +6,12 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Internal.Runtime.CompilerServices; namespace System.Text.Unicode { - internal static class Utf8Utility + internal static partial class Utf8Utility { /// /// The maximum number of bytes that can result from UTF-8 transcoding @@ -29,26 +31,16 @@ internal static class Utf8Utility /// comes first) is ASCII. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetIndexOfFirstInvalidUtf8Sequence(ReadOnlySpan utf8Data, out bool isAscii) + public unsafe static int GetIndexOfFirstInvalidUtf8Sequence(ReadOnlySpan utf8Data, out bool isAscii) { - // TODO_UTF8STRING: Replace this with the faster drop-in replacement when it's available (coreclr #21948). - - bool tempIsAscii = true; - int originalDataLength = utf8Data.Length; - - while (!utf8Data.IsEmpty) + fixed (byte* pUtf8Data = &MemoryMarshal.GetReference(utf8Data)) { - if (Rune.DecodeFromUtf8(utf8Data, out Rune result, out int bytesConsumed) != OperationStatus.Done) - { - break; - } + byte* pFirstInvalidByte = GetPointerToFirstInvalidByte(pUtf8Data, utf8Data.Length, out int utf16CodeUnitCountAdjustment, out _); + int index = (int)(void*)Unsafe.ByteOffset(ref *pUtf8Data, ref *pFirstInvalidByte); - tempIsAscii &= result.IsAscii; - utf8Data = utf8Data.Slice(bytesConsumed); + isAscii = (utf16CodeUnitCountAdjustment == 0); // If UTF-16 char count == UTF-8 byte count, it's ASCII. + return (index < utf8Data.Length) ? index : -1; } - - isAscii = tempIsAscii; - return (utf8Data.IsEmpty) ? -1 : (originalDataLength - utf8Data.Length); } #if FEATURE_UTF8STRING diff --git a/src/Common/src/CoreLib/System/Threading/Tasks/FutureFactory.cs b/src/Common/src/CoreLib/System/Threading/Tasks/FutureFactory.cs index c8145d87c6bc..e96fe14b3432 100644 --- a/src/Common/src/CoreLib/System/Threading/Tasks/FutureFactory.cs +++ b/src/Common/src/CoreLib/System/Threading/Tasks/FutureFactory.cs @@ -683,7 +683,7 @@ internal static Task FromAsyncImpl( asyncResult.AsyncWaitHandle, delegate { - try { t.InternalRunSynchronously(scheduler, waitForCompletion: false); } + try { t.InternalRunSynchronously(scheduler!, waitForCompletion: false); } // TODO-NULLABLE: https://github.com/dotnet/csharplang/issues/538 catch (Exception e) { promise.TrySetException(e); } // catch and log any scheduler exceptions }, null, diff --git a/src/Common/src/CoreLib/System/Threading/Tasks/Task.cs b/src/Common/src/CoreLib/System/Threading/Tasks/Task.cs index a8aac049361c..2c35214248ff 100644 --- a/src/Common/src/CoreLib/System/Threading/Tasks/Task.cs +++ b/src/Common/src/CoreLib/System/Threading/Tasks/Task.cs @@ -4477,7 +4477,8 @@ internal void RemoveContinuation(object continuationObject) // could be TaskCont // Task is completed. Nothing to do here. if (continuationsLocalRef == s_taskCompletionSentinel) return; - if (!(continuationsLocalRef is List continuationsLocalListRef)) + List? continuationsLocalListRef = continuationsLocalRef as List; + if (continuationsLocalListRef is null) { // This is not a list. If we have a single object (the one we want to remove) we try to replace it with an empty list. // Note we cannot go back to a null state, since it will mess up the AddTaskContinuation logic. diff --git a/src/Common/src/CoreLib/System/Threading/ThreadPool.cs b/src/Common/src/CoreLib/System/Threading/ThreadPool.cs index 4e6ff27e30eb..0625a97f13de 100644 --- a/src/Common/src/CoreLib/System/Threading/ThreadPool.cs +++ b/src/Common/src/CoreLib/System/Threading/ThreadPool.cs @@ -709,8 +709,9 @@ public ThreadPoolWorkQueueThreadLocals(ThreadPoolWorkQueue tpq) currentThread = Thread.CurrentThread; } - private void CleanUp() + ~ThreadPoolWorkQueueThreadLocals() { + // Transfer any pending workitems into the global queue so that they will be executed by another thread if (null != workStealingQueue) { if (null != workQueue) @@ -726,17 +727,6 @@ private void CleanUp() ThreadPoolWorkQueue.WorkStealingQueueList.Remove(workStealingQueue); } } - - ~ThreadPoolWorkQueueThreadLocals() - { - // Since the purpose of calling CleanUp is to transfer any pending workitems into the global - // queue so that they will be executed by another thread, there's no point in doing this cleanup - // if we're in the process of shutting down or unloading the AD. In those cases, the work won't - // execute anyway. And there are subtle race conditions involved there that would lead us to do the wrong - // thing anyway. So we'll only clean up if this is a "normal" finalization. - if (!Environment.HasShutdownStarted) - CleanUp(); - } } public delegate void WaitCallback(object? state); @@ -752,8 +742,7 @@ internal abstract class QueueUserWorkItemCallbackBase : IThreadPoolWorkItem ~QueueUserWorkItemCallbackBase() { Debug.Assert( - executed != 0 || Environment.HasShutdownStarted, - "A QueueUserWorkItemCallback was never called!"); + executed != 0, "A QueueUserWorkItemCallback was never called!"); } #endif diff --git a/src/Common/src/CoreLib/System/Threading/Timer.cs b/src/Common/src/CoreLib/System/Threading/Timer.cs index 66d01ac81ae1..19bdac6f7ada 100644 --- a/src/Common/src/CoreLib/System/Threading/Timer.cs +++ b/src/Common/src/CoreLib/System/Threading/Timer.cs @@ -678,17 +678,6 @@ public TimerHolder(TimerQueueTimer timer) ~TimerHolder() { - // If shutdown has started, another thread may be suspended while holding the timer lock. - // So we can't safely close the timer. - // - // Similarly, we should not close the timer during AD-unload's live-object finalization phase. - // A rude abort may have prevented us from releasing the lock. - // - // Note that in either case, the Timer still won't fire, because ThreadPool threads won't be - // allowed to run anymore. - if (Environment.HasShutdownStarted) - return; - _timer.Close(); } diff --git a/src/Common/src/CoreLib/System/Threading/TimerQueue.Windows.cs b/src/Common/src/CoreLib/System/Threading/TimerQueue.Windows.cs new file mode 100644 index 000000000000..0bd0cc49cf70 --- /dev/null +++ b/src/Common/src/CoreLib/System/Threading/TimerQueue.Windows.cs @@ -0,0 +1,40 @@ +// 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.Runtime.InteropServices; + +namespace System.Threading +{ + internal partial class TimerQueue + { + private static int TickCount + { + get + { + // We need to keep our notion of time synchronized with the calls to SleepEx that drive + // the underlying native timer. In Win8, SleepEx does not count the time the machine spends + // sleeping/hibernating. Environment.TickCount (GetTickCount) *does* count that time, + // so we will get out of sync with SleepEx if we use that method. + // + // So, on Win8, we use QueryUnbiasedInterruptTime instead; this does not count time spent + // in sleep/hibernate mode. + if (Environment.IsWindows8OrAbove) + { + ulong time100ns; + + bool result = Interop.Kernel32.QueryUnbiasedInterruptTime(out time100ns); + if (!result) + Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); + + // convert to 100ns to milliseconds, and truncate to 32 bits. + return (int)(uint)(time100ns / 10000); + } + else + { + return Environment.TickCount; + } + } + } + } +} diff --git a/src/Common/src/CoreLib/System/Type.cs b/src/Common/src/CoreLib/System/Type.cs index 8b19fbca52ac..d2a31cbf675b 100644 --- a/src/Common/src/CoreLib/System/Type.cs +++ b/src/Common/src/CoreLib/System/Type.cs @@ -281,8 +281,9 @@ public static TypeCode GetTypeCode(Type type) } protected virtual TypeCode GetTypeCodeImpl() { - if (this != UnderlyingSystemType && UnderlyingSystemType != null) - return Type.GetTypeCode(UnderlyingSystemType); + Type systemType = UnderlyingSystemType; + if (this != systemType && systemType != null) + return Type.GetTypeCode(systemType); return TypeCode.Object; } diff --git a/src/Common/src/CoreLib/System/Version.cs b/src/Common/src/CoreLib/System/Version.cs index 359837f348c7..f625f6fa4d78 100644 --- a/src/Common/src/CoreLib/System/Version.cs +++ b/src/Common/src/CoreLib/System/Version.cs @@ -429,15 +429,21 @@ private static bool TryParseComponent(ReadOnlySpan component, string compo public static bool operator <(Version v1, Version v2) { - if ((object)v1 == null) - throw new ArgumentNullException(nameof(v1)); + if (v1 is null) + { + return !(v2 is null); + } + return (v1.CompareTo(v2) < 0); } public static bool operator <=(Version v1, Version v2) { - if ((object)v1 == null) - throw new ArgumentNullException(nameof(v1)); + if (v1 is null) + { + return true; + } + return (v1.CompareTo(v2) <= 0); } diff --git a/src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Encoder.cs b/src/Common/src/Interop/Interop.Brotli.cs similarity index 62% rename from src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Encoder.cs rename to src/Common/src/Interop/Interop.Brotli.cs index 64906cebc178..e6314fc1cf81 100644 --- a/src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Encoder.cs +++ b/src/Common/src/Interop/Interop.Brotli.cs @@ -10,8 +10,25 @@ internal static partial class Interop { - internal static partial class Brotli + internal static class Brotli { + [DllImport(Libraries.CompressionNative)] + internal static extern SafeBrotliDecoderHandle BrotliDecoderCreateInstance(IntPtr allocFunc, IntPtr freeFunc, IntPtr opaque); + + [DllImport(Libraries.CompressionNative)] + internal static extern unsafe int BrotliDecoderDecompressStream( + SafeBrotliDecoderHandle state, ref size_t availableIn, byte** nextIn, + ref size_t availableOut, byte** nextOut, out size_t totalOut); + + [DllImport(Libraries.CompressionNative)] + internal static extern unsafe bool BrotliDecoderDecompress(size_t availableInput, byte* inBytes, ref size_t availableOutput, byte* outBytes); + + [DllImport(Libraries.CompressionNative)] + internal static extern void BrotliDecoderDestroyInstance(IntPtr state); + + [DllImport(Libraries.CompressionNative)] + internal static extern bool BrotliDecoderIsFinished(SafeBrotliDecoderHandle state); + [DllImport(Libraries.CompressionNative)] internal static extern SafeBrotliEncoderHandle BrotliEncoderCreateInstance(IntPtr allocFunc, IntPtr freeFunc, IntPtr opaque); diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs index d0c13510c015..dc6080646700 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFArray.cs @@ -38,11 +38,16 @@ namespace Microsoft.Win32.SafeHandles { internal sealed class SafeCFArrayHandle : SafeHandle { - internal SafeCFArrayHandle() + private SafeCFArrayHandle() : base(IntPtr.Zero, ownsHandle: true) { } + internal SafeCFArrayHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + protected override bool ReleaseHandle() { Interop.CoreFoundation.CFRelease(handle); diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs new file mode 100644 index 000000000000..79c2979732de --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFDictionary.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CoreFoundationLibrary)] + internal static extern IntPtr CFDictionaryGetValue(SafeCFDictionaryHandle handle, IntPtr key); + } +} + +namespace Microsoft.Win32.SafeHandles +{ + internal sealed class SafeCFDictionaryHandle : SafeHandle + { + private SafeCFDictionaryHandle() + : base(IntPtr.Zero, ownsHandle: true) + { + } + + internal SafeCFDictionaryHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + + protected override bool ReleaseHandle() + { + Interop.CoreFoundation.CFRelease(handle); + SetHandle(IntPtr.Zero); + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs new file mode 100644 index 000000000000..c2bffd316dc9 --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFNumber.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + internal enum CFNumberType + { + kCFNumberIntType = 9, + } + + [DllImport(Libraries.CoreFoundationLibrary)] + private static extern int CFNumberGetValue(IntPtr handle, CFNumberType type, out int value); + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs new file mode 100644 index 000000000000..1f6cc07064e5 --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFProxy.cs @@ -0,0 +1,145 @@ +// 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.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +using CFRunLoopSourceRef = System.IntPtr; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern SafeCFDictionaryHandle CFNetworkCopySystemProxySettings(); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern SafeCFArrayHandle CFNetworkCopyProxiesForURL(SafeCreateHandle url, SafeCFDictionaryHandle proxySettings); + + internal delegate void CFProxyAutoConfigurationResultCallback(IntPtr client, IntPtr proxyList, IntPtr error); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationURL( + IntPtr proxyAutoConfigURL, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [DllImport(Libraries.CFNetworkLibrary)] + internal static extern CFRunLoopSourceRef CFNetworkExecuteProxyAutoConfigurationScript( + IntPtr proxyAutoConfigurationScript, + SafeCreateHandle targetURL, + CFProxyAutoConfigurationResultCallback cb, + ref CFStreamClientContext clientContext); + + [StructLayout(LayoutKind.Sequential)] + internal struct CFStreamClientContext + { + public IntPtr Version; + public IntPtr Info; + public IntPtr Retain; + public IntPtr Release; + public IntPtr CopyDescription; + } + + internal class CFProxy + { + private SafeCFDictionaryHandle _dictionary; + + internal static readonly string kCFProxyTypeAutoConfigurationURL; + internal static readonly string kCFProxyTypeAutoConfigurationJavaScript; + internal static readonly string kCFProxyTypeFTP; + internal static readonly string kCFProxyTypeHTTP; + internal static readonly string kCFProxyTypeHTTPS; + internal static readonly string kCFProxyTypeSOCKS; + + private static readonly IntPtr kCFProxyAutoConfigurationJavaScriptKey; + private static readonly IntPtr kCFProxyAutoConfigurationURLKey; + private static readonly IntPtr kCFProxyHostNameKey; + private static readonly IntPtr kCFProxyPasswordKey; + private static readonly IntPtr kCFProxyPortNumberKey; + private static readonly IntPtr kCFProxyTypeKey; + private static readonly IntPtr kCFProxyUsernameKey; + + static CFProxy() + { + IntPtr lib = NativeLibrary.Load(Interop.Libraries.CFNetworkLibrary); + if (lib != IntPtr.Zero) + { + kCFProxyTypeAutoConfigurationURL = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationURL"); + kCFProxyTypeAutoConfigurationJavaScript = LoadCFStringSymbol(lib, "kCFProxyTypeAutoConfigurationJavaScript"); + kCFProxyTypeFTP = LoadCFStringSymbol(lib, "kCFProxyTypeFTP"); + kCFProxyTypeHTTP = LoadCFStringSymbol(lib, "kCFProxyTypeHTTP"); + kCFProxyTypeHTTPS = LoadCFStringSymbol(lib, "kCFProxyTypeHTTPS"); + kCFProxyTypeSOCKS = LoadCFStringSymbol(lib, "kCFProxyTypeSOCKS"); + + kCFProxyAutoConfigurationJavaScriptKey = LoadSymbol(lib, "kCFProxyAutoConfigurationJavaScriptKey"); + kCFProxyAutoConfigurationURLKey = LoadSymbol(lib, "kCFProxyAutoConfigurationURLKey"); + kCFProxyHostNameKey = LoadSymbol(lib, "kCFProxyHostNameKey"); + kCFProxyPasswordKey = LoadSymbol(lib, "kCFProxyPasswordKey"); + kCFProxyPortNumberKey = LoadSymbol(lib, "kCFProxyPortNumberKey"); + kCFProxyTypeKey = LoadSymbol(lib, "kCFProxyTypeKey"); + kCFProxyUsernameKey = LoadSymbol(lib, "kCFProxyUsernameKey"); + } + } + + public CFProxy(SafeCFDictionaryHandle dictionary) + { + _dictionary = dictionary; + } + + private static IntPtr LoadSymbol(IntPtr lib, string name) + { + IntPtr indirect = NativeLibrary.GetExport(lib, name); + return indirect == IntPtr.Zero ? IntPtr.Zero : Marshal.ReadIntPtr(indirect); + } + + private static string LoadCFStringSymbol(IntPtr lib, string name) + { + using (SafeCFStringHandle cfString = new SafeCFStringHandle(LoadSymbol(lib, name), false)) + { + Debug.Assert(!cfString.IsInvalid); + return Interop.CoreFoundation.CFStringToString(cfString); + } + } + + private string GetString(IntPtr key) + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, key); + if (dictValue != IntPtr.Zero) + { + using (SafeCFStringHandle handle = new SafeCFStringHandle(dictValue, false)) + { + return CFStringToString(handle); + } + } + return null; + } + + public string ProxyType => GetString(kCFProxyTypeKey); + public string HostName => GetString(kCFProxyHostNameKey); + public string Username => GetString(kCFProxyUsernameKey); + public string Password => GetString(kCFProxyPasswordKey); + + public int PortNumber + { + get + { + IntPtr dictValue = CFDictionaryGetValue(_dictionary, kCFProxyPortNumberKey); + if (dictValue != IntPtr.Zero && CFNumberGetValue(dictValue, CFNumberType.kCFNumberIntType, out int value) > 0) + { + return value; + } + return -1; + } + } + + public IntPtr AutoConfigurationURL => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationURLKey); + public IntPtr AutoConfigurationJavaScript => CFDictionaryGetValue(_dictionary, kCFProxyAutoConfigurationJavaScriptKey); + } + } +} diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs index aa7a15054cc5..17fb307d32d1 100644 --- a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFString.cs @@ -91,6 +91,11 @@ internal SafeCFStringHandle() { } + internal SafeCFStringHandle(IntPtr handle, bool ownsHandle) + : base(handle, ownsHandle) + { + } + protected override bool ReleaseHandle() { Interop.CoreFoundation.CFRelease(handle); diff --git a/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.cs b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.cs new file mode 100644 index 000000000000..88c2bbd0ac5b --- /dev/null +++ b/src/Common/src/Interop/OSX/Interop.CoreFoundation.CFUrl.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. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static partial class CoreFoundation + { + [DllImport(Libraries.CoreFoundationLibrary)] + private static extern SafeCreateHandle CFURLCreateWithString( + IntPtr allocator, + SafeCreateHandle str, + IntPtr baseUrl); + + internal static SafeCreateHandle CFURLCreateWithString(string url) + { + Debug.Assert(url != null); + using (SafeCreateHandle stringHandle = CFStringCreateWithCString(url)) + { + return CFURLCreateWithString(IntPtr.Zero, stringHandle, IntPtr.Zero); + } + } + } +} diff --git a/src/Common/src/Interop/OSX/Interop.Libraries.cs b/src/Common/src/Interop/OSX/Interop.Libraries.cs index 89f6a3f53755..ffac0b25dcde 100644 --- a/src/Common/src/Interop/OSX/Interop.Libraries.cs +++ b/src/Common/src/Interop/OSX/Interop.Libraries.cs @@ -7,7 +7,8 @@ internal static partial class Interop internal static partial class Libraries { internal const string CoreFoundationLibrary = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"; - internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; + internal const string CoreServicesLibrary = "/System/Library/Frameworks/CoreServices.framework/CoreServices"; + internal const string CFNetworkLibrary = "/System/Library/Frameworks/CFNetwork.framework/CFNetwork"; internal const string libproc = "libproc"; internal const string LibSystemCommonCrypto = "/usr/lib/system/libcommonCrypto"; internal const string LibSystemKernel = "/usr/lib/system/libsystem_kernel"; diff --git a/src/Common/src/Interop/OSX/Interop.RunLoop.cs b/src/Common/src/Interop/OSX/Interop.RunLoop.cs index 9257952b4499..f7c33442662c 100644 --- a/src/Common/src/Interop/OSX/Interop.RunLoop.cs +++ b/src/Common/src/Interop/OSX/Interop.RunLoop.cs @@ -34,6 +34,12 @@ internal static partial class RunLoop #endif internal extern static void CFRunLoopRun(); + /// + /// Runs the current thread’s CFRunLoop object in a particular mode. + /// + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal extern static int CFRunLoopRunInMode(CFStringRef mode, double seconds, int returnAfterSourceHandled); + /// /// Notifies a RunLoop to stop and return control to the execution context that called CFRunLoopRun /// @@ -66,7 +72,14 @@ internal static partial class RunLoop /// The run loop mode of rl from which to remove source. [DllImport(Interop.Libraries.CoreFoundationLibrary)] internal static extern void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); - + + /// + /// Invalidates a CFRunLoopSource object, stopping it from ever firing again. + /// + /// The run loop source to invalidate. + [DllImport(Interop.Libraries.CoreFoundationLibrary)] + internal static extern void CFRunLoopSourceInvalidate(CFRunLoopSourceRef source); + /// /// Returns a bool that indicates whether the run loop is waiting for an event. /// diff --git a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs index ed79d4987e65..d8e591c9b0a7 100644 --- a/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs +++ b/src/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Ssl.cs @@ -146,6 +146,9 @@ private static extern int AppleCryptoNative_SslIsHostnameMatch( [DllImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslGetProtocolVersion")] internal static extern int SslGetProtocolVersion(SafeSslHandle sslHandle, out SslProtocols protocol); + [DllImport(Interop.Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SslSetEnabledCipherSuites")] + unsafe internal static extern int SslSetEnabledCipherSuites(SafeSslHandle sslHandle, uint* cipherSuites, int numCipherSuites); + internal static void SslSetAcceptClientCert(SafeSslHandle sslHandle) { int osStatus = AppleCryptoNative_SslSetAcceptClientCert(sslHandle); diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.ReceiveMessage.cs b/src/Common/src/Interop/Unix/System.Native/Interop.ReceiveMessage.cs index 5a89ecf6de67..b603318247a6 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.ReceiveMessage.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.ReceiveMessage.cs @@ -11,6 +11,6 @@ internal static partial class Interop internal static partial class Sys { [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_ReceiveMessage")] - internal static extern unsafe Error ReceiveMessage(IntPtr socket, MessageHeader* messageHeader, SocketFlags flags, long* received); + internal static extern unsafe Error ReceiveMessage(SafeHandle socket, MessageHeader* messageHeader, SocketFlags flags, long* received); } } diff --git a/src/Common/src/Interop/Unix/System.Native/Interop.SendMessage.cs b/src/Common/src/Interop/Unix/System.Native/Interop.SendMessage.cs index 62cca3bdb592..4d728be10549 100644 --- a/src/Common/src/Interop/Unix/System.Native/Interop.SendMessage.cs +++ b/src/Common/src/Interop/Unix/System.Native/Interop.SendMessage.cs @@ -11,6 +11,6 @@ internal static partial class Interop internal static partial class Sys { [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SendMessage")] - internal static extern unsafe Error SendMessage(IntPtr socket, MessageHeader* messageHeader, SocketFlags flags, long* sent); + internal static extern unsafe Error SendMessage(SafeHandle socket, MessageHeader* messageHeader, SocketFlags flags, long* sent); } } diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs index c3c510500c65..cff89e4173ef 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs @@ -64,31 +64,55 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50 throw CreateSslException(SR.net_allocate_ssl_context_failed); } - // TLS 1.3 uses different ciphersuite restrictions than previous versions. - // It has no equivalent to a NoEncryption option. - if (policy == EncryptionPolicy.NoEncryption) + if (!Interop.Ssl.Tls13Supported) + { + if (protocols != SslProtocols.None && + CipherSuitesPolicyPal.WantsTls13(protocols)) + { + protocols = protocols & (~SslProtocols.Tls13); + } + } + else if (CipherSuitesPolicyPal.WantsTls13(protocols) && + CipherSuitesPolicyPal.ShouldOptOutOfTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) { if (protocols == SslProtocols.None) { + // we are using default settings but cipher suites policy says that TLS 1.3 + // is not compatible with our settings (i.e. we requested no encryption or disabled + // all TLS 1.3 cipher suites) protocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; } else { - protocols &= ~SslProtocols.Tls13; - - if (protocols == SslProtocols.None) - { - throw new SslException( - SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); - } + // user explicitly asks for TLS 1.3 but their policy is not compatible with TLS 1.3 + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } } + if (CipherSuitesPolicyPal.ShouldOptOutOfLowerThanTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) + { + if (!CipherSuitesPolicyPal.WantsTls13(protocols)) + { + // We cannot provide neither TLS 1.3 or non TLS 1.3, user disabled all cipher suites + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + + protocols = SslProtocols.Tls13; + } // Configure allowed protocols. It's ok to use DangerousGetHandle here without AddRef/Release as we just // create the handle, it's rooted by the using, no one else has a reference to it, etc. Ssl.SetProtocolOptions(innerContext.DangerousGetHandle(), protocols); + // Sets policy and security level + if (!Ssl.SetEncryptionPolicy(innerContext, policy)) + { + throw new SslException( + SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + // The logic in SafeSslHandle.Disconnect is simple because we are doing a quiet // shutdown (we aren't negotiating for session close to enable later session // restoration). @@ -98,10 +122,27 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50 // https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html Ssl.SslCtxSetQuietShutdown(innerContext); - if (!Ssl.SetEncryptionPolicy(innerContext, policy)) + byte[] cipherList = + CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); + + Debug.Assert(cipherList == null || (cipherList.Length >= 1 && cipherList[cipherList.Length - 1] == 0)); + + byte[] cipherSuites = + CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); + + Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0)); + + unsafe { - Crypto.ErrClearError(); - throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + fixed (byte* cipherListStr = cipherList) + fixed (byte* cipherSuitesStr = cipherSuites) + { + if (!Ssl.SetCiphers(innerContext, cipherListStr, cipherSuitesStr)) + { + Crypto.ErrClearError(); + throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); + } + } } bool hasCertificateAndKey = diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs index 4c686b2beb27..323a1b907d59 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; +using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using Microsoft.Win32.SafeHandles; @@ -135,6 +136,21 @@ internal static extern bool GetSslConnectionInfo( [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SslGetCurrentCipherId(SafeSslHandle ssl, out int cipherId); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_GetOpenSslCipherSuiteName")] + private static extern IntPtr GetOpenSslCipherSuiteName(SafeSslHandle ssl, int cipherSuite, out int isTls12OrLower); + + internal static string GetOpenSslCipherSuiteName(SafeSslHandle ssl, TlsCipherSuite cipherSuite, out bool isTls12OrLower) + { + string ret = Marshal.PtrToStringAnsi(GetOpenSslCipherSuiteName(ssl, (int)cipherSuite, out int isTls12OrLowerInt)); + isTls12OrLower = isTls12OrLowerInt != 0; + return ret; + } + + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_Tls13Supported")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool Tls13SupportedImpl(); + internal static readonly bool Tls13Supported = Tls13SupportedImpl(); + internal static SafeSharedX509NameStackHandle SslGetClientCAList(SafeSslHandle ssl) { Crypto.CheckValidOpenSslHandle(ssl); diff --git a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs index 4c4721f4128d..f8417342ef95 100644 --- a/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs +++ b/src/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.SslCtxOptions.cs @@ -30,6 +30,9 @@ internal static partial class Ssl [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslCtxSetVerify")] internal static extern void SslCtxSetVerify(SafeSslContextHandle ctx, SslCtxSetVerifyCallback callback); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetCiphers")] + internal static unsafe extern bool SetCiphers(SafeSslContextHandle ctx, byte* cipherList, byte* cipherSuites); + [DllImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SetEncryptionPolicy")] internal static extern bool SetEncryptionPolicy(SafeSslContextHandle ctx, EncryptionPolicy policy); } diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaString.cs b/src/Common/src/Interop/Windows/Advapi32/Interop.LSA_STRING.cs similarity index 97% rename from src/Common/src/Interop/Windows/SspiCli/Interop.LsaString.cs rename to src/Common/src/Interop/Windows/Advapi32/Interop.LSA_STRING.cs index 1c108df49064..7cd34c1f3f83 100644 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaString.cs +++ b/src/Common/src/Interop/Windows/Advapi32/Interop.LSA_STRING.cs @@ -7,7 +7,7 @@ internal partial class Interop { - internal partial class SspiCli + internal partial class Advapi32 { [StructLayout(LayoutKind.Sequential)] internal struct LSA_STRING diff --git a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs index dcb76bec8252..084dfee06931 100644 --- a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs +++ b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupNames2.cs @@ -15,9 +15,18 @@ internal static extern uint LsaLookupNames2( SafeLsaPolicyHandle handle, int flags, int count, - UNICODE_STRING[] names, + MARSHALLED_UNICODE_STRING[] names, out SafeLsaMemoryHandle referencedDomains, out SafeLsaMemoryHandle sids - ); + ); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct MARSHALLED_UNICODE_STRING + { + internal ushort Length; + internal ushort MaximumLength; + [MarshalAs(UnmanagedType.LPWStr)] + internal string Buffer; + } } } diff --git a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupSids.cs b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupSids.cs index 576d8e53bd74..b28132a7ad9b 100644 --- a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupSids.cs +++ b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaLookupSids.cs @@ -17,6 +17,6 @@ internal static extern uint LsaLookupSids( IntPtr[] sids, out SafeLsaMemoryHandle referencedDomains, out SafeLsaMemoryHandle names - ); + ); } } diff --git a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaOpenPolicy.cs b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaOpenPolicy.cs index ca7ae1caf2b5..4360f339b68f 100644 --- a/src/Common/src/Interop/Windows/Advapi32/Interop.LsaOpenPolicy.cs +++ b/src/Common/src/Interop/Windows/Advapi32/Interop.LsaOpenPolicy.cs @@ -11,6 +11,34 @@ internal static partial class Interop internal static partial class Advapi32 { [DllImport(Interop.Libraries.Advapi32, EntryPoint = "LsaOpenPolicy", SetLastError = true, CharSet = CharSet.Unicode)] - internal static extern uint LsaOpenPolicy(string systemName, ref LSA_OBJECT_ATTRIBUTES attributes, int accessMask, out SafeLsaPolicyHandle handle); + private static extern uint LsaOpenPolicy( + ref UNICODE_STRING SystemName, + ref OBJECT_ATTRIBUTES ObjectAttributes, + int AccessMask, + out SafeLsaPolicyHandle PolicyHandle + ); + + internal static unsafe uint LsaOpenPolicy( + string SystemName, + ref OBJECT_ATTRIBUTES Attributes, + int AccessMask, + out SafeLsaPolicyHandle PolicyHandle) + { + var systemNameUnicode = new UNICODE_STRING(); + if (SystemName != null) + { + fixed (char* c = SystemName) + { + systemNameUnicode.Length = checked((ushort)(SystemName.Length * sizeof(char))); + systemNameUnicode.MaximumLength = checked((ushort)(SystemName.Length * sizeof(char))); + systemNameUnicode.Buffer = (IntPtr)c; + return LsaOpenPolicy(ref systemNameUnicode, ref Attributes, AccessMask, out PolicyHandle); + } + } + else + { + return LsaOpenPolicy(ref systemNameUnicode, ref Attributes, AccessMask, out PolicyHandle); + } + } } } diff --git a/src/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs b/src/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs new file mode 100644 index 000000000000..c6e51ae89242 --- /dev/null +++ b/src/Common/src/Interop/Windows/Interop.OBJECT_ATTRIBUTES.cs @@ -0,0 +1,103 @@ +// 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.Runtime.InteropServices; + +internal static partial class Interop +{ + /// + /// OBJECT_ATTRIBUTES structure. + /// The OBJECT_ATTRIBUTES structure specifies attributes that can be applied to objects or object handles by routines + /// that create objects and/or return handles to objects. + /// + internal unsafe struct OBJECT_ATTRIBUTES + { + public uint Length; + + /// + /// Optional handle to root object directory for the given ObjectName. + /// Can be a file system directory or object manager directory. + /// + public IntPtr RootDirectory; + + /// + /// Name of the object. Must be fully qualified if RootDirectory isn't set. + /// Otherwise is relative to RootDirectory. + /// + public UNICODE_STRING* ObjectName; + + public ObjectAttributes Attributes; + + /// + /// If null, object will receive default security settings. + /// + public void* SecurityDescriptor; + + /// + /// Optional quality of service to be applied to the object. Used to indicate + /// security impersonation level and context tracking mode (dynamic or static). + /// + public void* SecurityQualityOfService; + + /// + /// Equivalent of InitializeObjectAttributes macro with the exception that you can directly set SQOS. + /// + public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory) + { + Length = (uint)sizeof(OBJECT_ATTRIBUTES); + RootDirectory = rootDirectory; + ObjectName = objectName; + Attributes = attributes; + SecurityDescriptor = null; + SecurityQualityOfService = null; + } + } + + [Flags] + public enum ObjectAttributes : uint + { + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564586.aspx + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff547804.aspx + + /// + /// This handle can be inherited by child processes of the current process. + /// + OBJ_INHERIT = 0x00000002, + + /// + /// This flag only applies to objects that are named within the object manager. + /// By default, such objects are deleted when all open handles to them are closed. + /// If this flag is specified, the object is not deleted when all open handles are closed. + /// + OBJ_PERMANENT = 0x00000010, + + /// + /// Only a single handle can be open for this object. + /// + OBJ_EXCLUSIVE = 0x00000020, + + /// + /// Lookups for this object should be case insensitive. + /// + OBJ_CASE_INSENSITIVE = 0x00000040, + + /// + /// Create on existing object should open, not fail with STATUS_OBJECT_NAME_COLLISION. + /// + OBJ_OPENIF = 0x00000080, + + /// + /// Open the symbolic link, not its target. + /// + OBJ_OPENLINK = 0x00000100, + + // Only accessible from kernel mode + // OBJ_KERNEL_HANDLE + + // Access checks enforced, even in kernel mode + // OBJ_FORCE_ACCESS_CHECK + // OBJ_VALID_ATTRIBUTES = 0x000001F2 + } +} diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.DCB.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.DCB.cs index 1e0ea25a546b..c2df705835f2 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.DCB.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.DCB.cs @@ -9,6 +9,47 @@ internal partial class Interop { internal partial class Kernel32 { + internal static class DCBFlags + { + // Since C# does not provide access to bitfields and the native DCB structure contains + // a very necessary one, these are the positional offsets (from the right) of areas + // of the 32-bit integer used in SerialStream's SetDcbFlag() and GetDcbFlag() methods. + internal const int FBINARY = 0; + internal const int FPARITY = 1; + internal const int FOUTXCTSFLOW = 2; + internal const int FOUTXDSRFLOW = 3; + internal const int FDTRCONTROL = 4; + internal const int FDSRSENSITIVITY = 6; + internal const int FOUTX = 8; + internal const int FINX = 9; + internal const int FERRORCHAR = 10; + internal const int FNULL = 11; + internal const int FRTSCONTROL = 12; + internal const int FABORTONOERROR = 14; + internal const int FDUMMY2 = 15; + } + + internal static class DCBDTRFlowControl + { + internal const int DTR_CONTROL_DISABLE = 0x00; + internal const int DTR_CONTROL_ENABLE = 0x01; + } + + internal static class DCBRTSFlowControl + { + internal const int RTS_CONTROL_DISABLE = 0x00; + internal const int RTS_CONTROL_ENABLE = 0x01; + internal const int RTS_CONTROL_HANDSHAKE = 0x02; + internal const int RTS_CONTROL_TOGGLE = 0x03; + } + + internal static class DCBStopBits + { + internal const byte ONESTOPBIT = 0; + internal const byte ONE5STOPBITS = 1; + internal const byte TWOSTOPBITS = 2; + } + // Declaration for C# representation of Win32 Device Control Block (DCB) // structure. Note that all flag properties are encapsulated in the Flags field here, // and accessed/set through SerialStream's GetDcbFlag(...) and SetDcbFlag(...) methods. @@ -16,6 +57,11 @@ internal partial class Kernel32 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa363214.aspx internal struct DCB { + internal const byte EOFCHAR = 26; + + internal const byte DEFAULTXONCHAR = (byte)17; + internal const byte DEFAULTXOFFCHAR = (byte)19; + public uint DCBlength; public uint BaudRate; public uint Flags; diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.EscapeCommFunction.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.EscapeCommFunction.cs index 6550ebc1154d..908a9216441b 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.EscapeCommFunction.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.EscapeCommFunction.cs @@ -9,6 +9,14 @@ internal partial class Interop { internal partial class Kernel32 { + internal static class CommFunctions + { + internal const int SETRTS = 3; // Set RTS high + internal const int CLRRTS = 4; // Set RTS low + internal const int SETDTR = 5; // Set DTR high + internal const int CLRDTR = 6; + } + [DllImport(Libraries.Kernel32, SetLastError=true, CharSet=CharSet.Auto)] internal static extern bool EscapeCommFunction( SafeFileHandle hFile, diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.GetCommModemStatus.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.GetCommModemStatus.cs index c15d4fe790b6..e7e3148f751f 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.GetCommModemStatus.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.GetCommModemStatus.cs @@ -9,6 +9,13 @@ internal partial class Interop { internal partial class Kernel32 { + internal static class CommModemState + { + internal const int MS_CTS_ON = 0x10; + internal const int MS_DSR_ON = 0x20; + internal const int MS_RLSD_ON = 0x80; + } + [DllImport(Libraries.Kernel32, SetLastError=true, CharSet=CharSet.Auto)] internal static extern bool GetCommModemStatus( SafeFileHandle hFile, diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.PurgeComm.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.PurgeComm.cs index 198ee1092d0b..8eaafbb761c3 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.PurgeComm.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.PurgeComm.cs @@ -9,6 +9,14 @@ internal partial class Interop { internal partial class Kernel32 { + internal static class PurgeFlags + { + internal const uint PURGE_TXABORT = 0x0001; // Kill the pending/current writes to the comm port. + internal const uint PURGE_RXABORT = 0x0002; // Kill the pending/current reads to the comm port. + internal const uint PURGE_TXCLEAR = 0x0004; // Kill the transmit queue if there. + internal const uint PURGE_RXCLEAR = 0x0008; // Kill the typeahead buffer if there. + } + [DllImport(Libraries.Kernel32, SetLastError=true, CharSet=CharSet.Auto)] internal static extern bool PurgeComm( SafeFileHandle hFile, diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommMask.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommMask.cs index 05472b0524f9..d3637e702d53 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommMask.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommMask.cs @@ -9,6 +9,13 @@ internal partial class Interop { internal partial class Kernel32 { + internal static class CommEvents + { + internal const int EV_RXCHAR = 0x01; + internal const int EV_ERR = 0x80; + internal const int ALL_EVENTS = 0x1fb; + } + [DllImport(Libraries.Kernel32, SetLastError=true, CharSet=CharSet.Auto)] internal static extern bool SetCommMask( SafeFileHandle hFile, diff --git a/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommTimeouts.cs b/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommTimeouts.cs index 9c38c9494bec..7d19d57c1716 100644 --- a/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommTimeouts.cs +++ b/src/Common/src/Interop/Windows/Kernel32/Interop.SetCommTimeouts.cs @@ -9,6 +9,8 @@ internal partial class Interop { internal partial class Kernel32 { + internal const int MAXDWORD = -1; // This is 0xfffffff, or UInt32.MaxValue, here used as an int + [DllImport(Libraries.Kernel32, SetLastError=true, CharSet=CharSet.Auto)] internal static extern bool SetCommTimeouts( SafeFileHandle hFile, diff --git a/src/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs b/src/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs index 967b7bf27f41..1fa7b7282688 100644 --- a/src/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs +++ b/src/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs @@ -68,100 +68,6 @@ internal unsafe static (int status, IntPtr handle) CreateFile( } } - /// - /// OBJECT_ATTRIBUTES structure. - /// The OBJECT_ATTRIBUTES structure specifies attributes that can be applied to objects or object handles by routines - /// that create objects and/or return handles to objects. - /// - internal unsafe struct OBJECT_ATTRIBUTES - { - public uint Length; - - /// - /// Optional handle to root object directory for the given ObjectName. - /// Can be a file system directory or object manager directory. - /// - public IntPtr RootDirectory; - - /// - /// Name of the object. Must be fully qualified if RootDirectory isn't set. - /// Otherwise is relative to RootDirectory. - /// - public UNICODE_STRING* ObjectName; - - public ObjectAttributes Attributes; - - /// - /// If null, object will receive default security settings. - /// - public void* SecurityDescriptor; - - /// - /// Optional quality of service to be applied to the object. Used to indicate - /// security impersonation level and context tracking mode (dynamic or static). - /// - public void* SecurityQualityOfService; - - /// - /// Equivalent of InitializeObjectAttributes macro with the exception that you can directly set SQOS. - /// - public unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, ObjectAttributes attributes, IntPtr rootDirectory) - { - Length = (uint)sizeof(OBJECT_ATTRIBUTES); - RootDirectory = rootDirectory; - ObjectName = objectName; - Attributes = attributes; - SecurityDescriptor = null; - SecurityQualityOfService = null; - } - } - - [Flags] - public enum ObjectAttributes : uint - { - // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564586.aspx - // https://msdn.microsoft.com/en-us/library/windows/hardware/ff547804.aspx - - /// - /// This handle can be inherited by child processes of the current process. - /// - OBJ_INHERIT = 0x00000002, - - /// - /// This flag only applies to objects that are named within the object manager. - /// By default, such objects are deleted when all open handles to them are closed. - /// If this flag is specified, the object is not deleted when all open handles are closed. - /// - OBJ_PERMANENT = 0x00000010, - - /// - /// Only a single handle can be open for this object. - /// - OBJ_EXCLUSIVE = 0x00000020, - - /// - /// Lookups for this object should be case insensitive. - /// - OBJ_CASE_INSENSITIVE = 0x00000040, - - /// - /// Create on existing object should open, not fail with STATUS_OBJECT_NAME_COLLISION. - /// - OBJ_OPENIF = 0x00000080, - - /// - /// Open the symbolic link, not its target. - /// - OBJ_OPENLINK = 0x00000100, - - // Only accessible from kernel mode - // OBJ_KERNEL_HANDLE - - // Access checks enforced, even in kernel mode - // OBJ_FORCE_ACCESS_CHECK - // OBJ_VALID_ATTRIBUTES = 0x000001F2 - } - /// /// File creation disposition when calling directly to NT APIs. /// diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.KerbS4uLogin.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.KerbS4uLogin.cs index 68e0022dc90d..2e9dd978554b 100644 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.KerbS4uLogin.cs +++ b/src/Common/src/Interop/Windows/SspiCli/Interop.KerbS4uLogin.cs @@ -14,8 +14,8 @@ internal struct KERB_S4U_LOGON { internal KERB_LOGON_SUBMIT_TYPE MessageType; internal KerbS4uLogonFlags Flags; - internal LSA_UNICODE_STRING ClientUpn; - internal LSA_UNICODE_STRING ClientRealm; + internal UNICODE_STRING ClientUpn; + internal UNICODE_STRING ClientRealm; } [Flags] diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.LSAStructs.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.LSAStructs.cs index 3d0d6a665813..bba8c144957f 100644 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.LSAStructs.cs +++ b/src/Common/src/Interop/Windows/SspiCli/Interop.LSAStructs.cs @@ -17,17 +17,6 @@ internal struct LSA_TRANSLATED_NAME internal int DomainIndex; } - [StructLayout(LayoutKind.Sequential)] - internal struct LSA_OBJECT_ATTRIBUTES - { - internal int Length; - internal IntPtr RootDirectory; - internal IntPtr ObjectName; - internal int Attributes; - internal IntPtr SecurityDescriptor; - internal IntPtr SecurityQualityOfService; - } - [StructLayout(LayoutKind.Sequential)] internal struct LSA_TRANSLATED_SID2 { diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLogonUser.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLogonUser.cs index 86d4be2b08ad..268688c42de4 100644 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLogonUser.cs +++ b/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLogonUser.cs @@ -14,7 +14,7 @@ internal partial class SspiCli [DllImport(Libraries.SspiCli)] internal static extern int LsaLogonUser( [In] SafeLsaHandle LsaHandle, - [In] ref LSA_STRING OriginName, + [In] ref Advapi32.LSA_STRING OriginName, [In] SECURITY_LOGON_TYPE LogonType, [In] int AuthenticationPackage, [In] IntPtr AuthenticationInformation, diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLookupAuthenticationPackage.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLookupAuthenticationPackage.cs index 950cde7553d1..60802408a28f 100644 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLookupAuthenticationPackage.cs +++ b/src/Common/src/Interop/Windows/SspiCli/Interop.LsaLookupAuthenticationPackage.cs @@ -4,7 +4,6 @@ using System; using System.Runtime.InteropServices; - using Microsoft.Win32.SafeHandles; internal partial class Interop @@ -12,6 +11,10 @@ internal partial class Interop internal partial class SspiCli { [DllImport(Libraries.SspiCli)] - internal static extern int LsaLookupAuthenticationPackage(SafeLsaHandle LsaHandle, [In] ref LSA_STRING PackageName, out int AuthenticationPackage); + internal static extern int LsaLookupAuthenticationPackage( + SafeLsaHandle LsaHandle, + [In] ref Advapi32.LSA_STRING PackageName, + out int AuthenticationPackage + ); } } diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaUnicodeString.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.LsaUnicodeString.cs deleted file mode 100644 index 205fa8ae1e11..000000000000 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.LsaUnicodeString.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Runtime.InteropServices; - -internal partial class Interop -{ - internal partial class SspiCli - { - [StructLayout(LayoutKind.Sequential)] - internal struct LSA_UNICODE_STRING - { - internal LSA_UNICODE_STRING(IntPtr pBuffer, ushort length) - { - Length = length; - MaximumLength = length; - Buffer = pBuffer; - } - - /// - /// Specifies the length, in bytes, of the string in Buffer. This value does not include the terminating null character, if any. - /// - internal ushort Length; - - /// - /// Specifies the total size, in bytes, of Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory. - /// - internal ushort MaximumLength; - - /// - /// Pointer to a wide character string. Note that strings returned by the LSA may not be null-terminated. - /// - internal IntPtr Buffer; - } - } -} diff --git a/src/Common/src/Interop/Windows/SspiCli/Interop.UNICODE_STRING.cs b/src/Common/src/Interop/Windows/SspiCli/Interop.UNICODE_STRING.cs deleted file mode 100644 index e81b5063e9e3..000000000000 --- a/src/Common/src/Interop/Windows/SspiCli/Interop.UNICODE_STRING.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.Runtime.InteropServices; - -internal static partial class Interop -{ - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct UNICODE_STRING - { - internal ushort Length; - internal ushort MaximumLength; - [MarshalAs(UnmanagedType.LPWStr)] - internal string Buffer; - } -} diff --git a/src/Common/src/Interop/Windows/WinSock/Interop.WSARecv.cs b/src/Common/src/Interop/Windows/WinSock/Interop.WSARecv.cs index fc740044cfcc..fbd8dd4ae4fc 100644 --- a/src/Common/src/Interop/Windows/WinSock/Interop.WSARecv.cs +++ b/src/Common/src/Interop/Windows/WinSock/Interop.WSARecv.cs @@ -12,6 +12,16 @@ internal static partial class Interop { internal static partial class Winsock { + [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)] + internal static unsafe extern SocketError WSARecv( + SafeHandle socketHandle, + WSABuffer* buffer, + int bufferCount, + out int bytesTransferred, + ref SocketFlags socketFlags, + NativeOverlapped* overlapped, + IntPtr completionRoutine); + [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)] internal static unsafe extern SocketError WSARecv( IntPtr socketHandle, @@ -23,7 +33,7 @@ internal static unsafe extern SocketError WSARecv( IntPtr completionRoutine); internal static unsafe SocketError WSARecv( - IntPtr socketHandle, + SafeHandle socketHandle, ref WSABuffer buffer, int bufferCount, out int bytesTransferred, @@ -38,6 +48,22 @@ internal static unsafe SocketError WSARecv( return WSARecv(socketHandle, &localBuffer, bufferCount, out bytesTransferred, ref socketFlags, overlapped, completionRoutine); } + internal static unsafe SocketError WSARecv( + SafeHandle socketHandle, + Span buffers, + int bufferCount, + out int bytesTransferred, + ref SocketFlags socketFlags, + NativeOverlapped* overlapped, + IntPtr completionRoutine) + { + Debug.Assert(!buffers.IsEmpty); + fixed (WSABuffer* buffersPtr = &MemoryMarshal.GetReference(buffers)) + { + return WSARecv(socketHandle, buffersPtr, bufferCount, out bytesTransferred, ref socketFlags, overlapped, completionRoutine); + } + } + internal static unsafe SocketError WSARecv( IntPtr socketHandle, Span buffers, diff --git a/src/Common/src/Interop/Windows/WinSock/Interop.WSARecvFrom.cs b/src/Common/src/Interop/Windows/WinSock/Interop.WSARecvFrom.cs index 6f2eb2cb64b5..d880e8ec20c0 100644 --- a/src/Common/src/Interop/Windows/WinSock/Interop.WSARecvFrom.cs +++ b/src/Common/src/Interop/Windows/WinSock/Interop.WSARecvFrom.cs @@ -14,7 +14,7 @@ internal static partial class Winsock { [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)] private static unsafe extern SocketError WSARecvFrom( - IntPtr socketHandle, + SafeHandle socketHandle, WSABuffer* buffers, int bufferCount, out int bytesTransferred, @@ -25,7 +25,7 @@ private static unsafe extern SocketError WSARecvFrom( IntPtr completionRoutine); internal static unsafe SocketError WSARecvFrom( - IntPtr socketHandle, + SafeHandle socketHandle, ref WSABuffer buffer, int bufferCount, out int bytesTransferred, @@ -43,7 +43,7 @@ internal static unsafe SocketError WSARecvFrom( } internal static unsafe SocketError WSARecvFrom( - IntPtr socketHandle, + SafeHandle socketHandle, WSABuffer[] buffers, int bufferCount, out int bytesTransferred, diff --git a/src/Common/src/Interop/Windows/WinSock/Interop.WSASend.cs b/src/Common/src/Interop/Windows/WinSock/Interop.WSASend.cs index c2fa3b9a50ea..ee6f8d0f6aa7 100644 --- a/src/Common/src/Interop/Windows/WinSock/Interop.WSASend.cs +++ b/src/Common/src/Interop/Windows/WinSock/Interop.WSASend.cs @@ -22,8 +22,18 @@ internal static extern unsafe SocketError WSASend( NativeOverlapped* overlapped, IntPtr completionRoutine); + [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)] + internal static extern unsafe SocketError WSASend( + SafeHandle socketHandle, + WSABuffer* buffers, + int bufferCount, + out int bytesTransferred, + SocketFlags socketFlags, + NativeOverlapped* overlapped, + IntPtr completionRoutine); + internal static unsafe SocketError WSASend( - IntPtr socketHandle, + SafeHandle socketHandle, ref WSABuffer buffer, int bufferCount, out int bytesTransferred, @@ -38,6 +48,22 @@ internal static unsafe SocketError WSASend( return WSASend(socketHandle, &localBuffer, bufferCount, out bytesTransferred, socketFlags, overlapped, completionRoutine); } + internal static unsafe SocketError WSASend( + SafeHandle socketHandle, + Span buffers, + int bufferCount, + out int bytesTransferred, + SocketFlags socketFlags, + NativeOverlapped* overlapped, + IntPtr completionRoutine) + { + Debug.Assert(!buffers.IsEmpty); + fixed (WSABuffer* buffersPtr = &MemoryMarshal.GetReference(buffers)) + { + return WSASend(socketHandle, buffersPtr, bufferCount, out bytesTransferred, socketFlags, overlapped, completionRoutine); + } + } + internal static unsafe SocketError WSASend( IntPtr socketHandle, Span buffers, diff --git a/src/Common/src/Interop/Windows/WinSock/Interop.WSASendTo.cs b/src/Common/src/Interop/Windows/WinSock/Interop.WSASendTo.cs index e0a33aaf9600..66f7b220fa0a 100644 --- a/src/Common/src/Interop/Windows/WinSock/Interop.WSASendTo.cs +++ b/src/Common/src/Interop/Windows/WinSock/Interop.WSASendTo.cs @@ -14,7 +14,7 @@ internal static partial class Winsock { [DllImport(Interop.Libraries.Ws2_32, SetLastError = true)] private static unsafe extern SocketError WSASendTo( - IntPtr socketHandle, + SafeHandle socketHandle, WSABuffer* buffers, int bufferCount, out int bytesTransferred, @@ -25,7 +25,7 @@ private static unsafe extern SocketError WSASendTo( IntPtr completionRoutine); internal static unsafe SocketError WSASendTo( - IntPtr socketHandle, + SafeHandle socketHandle, ref WSABuffer buffer, int bufferCount, out int bytesTransferred, @@ -43,7 +43,7 @@ internal static unsafe SocketError WSASendTo( } internal static unsafe SocketError WSASendTo( - IntPtr socketHandle, + SafeHandle socketHandle, WSABuffer[] buffers, int bufferCount, [Out] out int bytesTransferred, diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs new file mode 100644 index 000000000000..d7beb5865afd --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaMemoryHandle.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; + +namespace Microsoft.Win32.SafeHandles +{ + internal sealed class SafeLsaMemoryHandle : SafeBuffer + { + private SafeLsaMemoryHandle() : base(true) { } + + // 0 is an Invalid Handle + internal SafeLsaMemoryHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + override protected bool ReleaseHandle() + { + return Interop.Advapi32.LsaFreeMemory(handle) == 0; + } + } +} diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.cs new file mode 100644 index 000000000000..856bea9d7174 --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaPolicyHandle.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. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Win32.SafeHandles +{ + internal sealed class SafeLsaPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid + { + private SafeLsaPolicyHandle() : base(true) { } + + // 0 is an Invalid Handle + internal SafeLsaPolicyHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + override protected bool ReleaseHandle() + { + return Interop.Advapi32.LsaClose(handle) == 0; + } + } +} diff --git a/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs new file mode 100644 index 000000000000..d310e6aef6ca --- /dev/null +++ b/src/Common/src/Microsoft/Win32/SafeHandles/SafeLsaReturnBufferHandle.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Microsoft.Win32.SafeHandles +{ + internal sealed class SafeLsaReturnBufferHandle : SafeBuffer + { + private SafeLsaReturnBufferHandle() : base(true) { } + + // 0 is an Invalid Handle + internal SafeLsaReturnBufferHandle(IntPtr handle) : base(true) + { + SetHandle(handle); + } + + override protected bool ReleaseHandle() + { + // LsaFreeReturnBuffer returns an NTSTATUS + return Interop.SspiCli.LsaFreeReturnBuffer(handle) >= 0; + } + } +} diff --git a/src/System.Security.AccessControl/src/System/Security/SafeSecurityHandles.cs b/src/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs similarity index 100% rename from src/System.Security.AccessControl/src/System/Security/SafeSecurityHandles.cs rename to src/Common/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs diff --git a/src/Common/src/System/Buffers/ArrayBufferWriter.cs b/src/Common/src/System/Buffers/ArrayBufferWriter.cs new file mode 100644 index 000000000000..193eab829ca4 --- /dev/null +++ b/src/Common/src/System/Buffers/ArrayBufferWriter.cs @@ -0,0 +1,185 @@ +// 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.Diagnostics; + +namespace System.Buffers +{ + /// + /// Represents a heap-based, array-backed output sink into which data can be written. + /// +#if USE_ABW_INTERNALLY + internal +#else + public +#endif + sealed class ArrayBufferWriter : IBufferWriter + { + private T[] _buffer; + private int _index; + + private const int MinimumBufferSize = 256; + + /// + /// Creates an instance of an , in which data can be written to, + /// with the default initial capacity. + /// + public ArrayBufferWriter() + { + _buffer = new T[MinimumBufferSize]; + _index = 0; + } + + /// + /// Creates an instance of an , in which data can be written to, + /// with an initial capacity specified. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// Thrown when is not positive (i.e. less than or equal to 0). + /// + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + throw new ArgumentException(nameof(initialCapacity)); + + _buffer = new T[initialCapacity]; + _index = 0; + } + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _index); + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _index); + + /// + /// Returns the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => _index; + + /// + /// Returns the total amount of space within the underlying buffer. + /// + public int Capacity => _buffer.Length; + + /// + /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => _buffer.Length - _index; + + /// + /// Clears the data written to the underlying buffer. + /// + /// + /// You must clear the before trying to re-use it. + /// + public void Clear() + { + Debug.Assert(_buffer.Length >= _index); + _buffer.AsSpan(0, _index).Clear(); + _index = 0; + } + + /// + /// Notifies that amount of data was written to the output / + /// + /// + /// Thrown when is negative. + /// + /// + /// Thrown when attempting to advance past the end of the underlying buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public void Advance(int count) + { + if (count < 0) + throw new ArgumentException(nameof(count)); + + if (_index > _buffer.Length - count) + ThrowInvalidOperationException(_buffer.Length); + + _index += count; + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsMemory(_index); + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsSpan(_index); + } + + private void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + throw new ArgumentException(nameof(sizeHint)); + + if (sizeHint == 0) + { + sizeHint = MinimumBufferSize; + } + + if (sizeHint > FreeCapacity) + { + int growBy = Math.Max(sizeHint, _buffer.Length); + + int newSize = checked(_buffer.Length + growBy); + + Array.Resize(ref _buffer, newSize); + } + + Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); + } + + private static void ThrowInvalidOperationException(int capacity) + { + throw new InvalidOperationException(SR.Format(SR.BufferWriterAdvancedTooFar, capacity)); + } + } +} diff --git a/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs index 86c5ba9e50ae..f422f5e33e91 100644 --- a/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs +++ b/src/Common/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -782,7 +782,11 @@ private async Task HandleReceivedCloseAsync(MessageHeader header, CancellationTo lock (StateUpdateLock) { _receivedCloseFrame = true; - if (_state < WebSocketState.CloseReceived) + if (_sentCloseFrame && _state < WebSocketState.Closed) + { + _state = WebSocketState.Closed; + } + else if (_state < WebSocketState.CloseReceived) { _state = WebSocketState.CloseReceived; } @@ -1059,50 +1063,56 @@ private async Task CloseAsyncPrivate(WebSocketCloseStatus closeStatus, string st await SendCloseFrameAsync(closeStatus, statusDescription, cancellationToken).ConfigureAwait(false); } - // We should now either be in a CloseSent case (because we just sent one), or in a CloseReceived state, in case + // We should now either be in a CloseSent case (because we just sent one), or in a Closed state, in case // there was a concurrent receive that ended up handling an immediate close frame response from the server. // Of course it could also be Aborted if something happened concurrently to cause things to blow up. Debug.Assert( State == WebSocketState.CloseSent || - State == WebSocketState.CloseReceived || + State == WebSocketState.Closed || State == WebSocketState.Aborted, $"Unexpected state {State}."); - // Wait until we've received a close response - byte[] closeBuffer = ArrayPool.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength); - try + // We only need to wait for a received close frame if we are in the CloseSent State. If we are in the Closed + // State then it means we already received a close frame. If we are in the Aborted State, then we should not + // wait for a close frame as per RFC 6455 Section 7.1.7 "Fail the WebSocket Connection". + if (State == WebSocketState.CloseSent) { - while (!_receivedCloseFrame) + // Wait until we've received a close response + byte[] closeBuffer = ArrayPool.Shared.Rent(MaxMessageHeaderLength + MaxControlPayloadLength); + try { - Debug.Assert(!Monitor.IsEntered(StateUpdateLock), $"{nameof(StateUpdateLock)} must never be held when acquiring {nameof(ReceiveAsyncLock)}"); - Task receiveTask; - lock (ReceiveAsyncLock) + while (!_receivedCloseFrame) { - // Now that we're holding the ReceiveAsyncLock, double-check that we've not yet received the close frame. - // It could have been received between our check above and now due to a concurrent receive completing. - if (_receivedCloseFrame) + Debug.Assert(!Monitor.IsEntered(StateUpdateLock), $"{nameof(StateUpdateLock)} must never be held when acquiring {nameof(ReceiveAsyncLock)}"); + Task receiveTask; + lock (ReceiveAsyncLock) { - break; + // Now that we're holding the ReceiveAsyncLock, double-check that we've not yet received the close frame. + // It could have been received between our check above and now due to a concurrent receive completing. + if (_receivedCloseFrame) + { + break; + } + + // We've not yet processed a received close frame, which means we need to wait for a received close to complete. + // There may already be one in flight, in which case we want to just wait for that one rather than kicking off + // another (we don't support concurrent receive operations). We need to kick off a new receive if either we've + // never issued a receive or if the last issued receive completed for reasons other than a close frame. There is + // a race condition here, e.g. if there's a in-flight receive that completes after we check, but that's fine: worst + // case is we then await it, find that it's not what we need, and try again. + receiveTask = _lastReceiveAsync; + _lastReceiveAsync = receiveTask = ValidateAndReceiveAsync(receiveTask, closeBuffer, cancellationToken); } - // We've not yet processed a received close frame, which means we need to wait for a received close to complete. - // There may already be one in flight, in which case we want to just wait for that one rather than kicking off - // another (we don't support concurrent receive operations). We need to kick off a new receive if either we've - // never issued a receive or if the last issued receive completed for reasons other than a close frame. There is - // a race condition here, e.g. if there's a in-flight receive that completes after we check, but that's fine: worst - // case is we then await it, find that it's not what we need, and try again. - receiveTask = _lastReceiveAsync; - _lastReceiveAsync = receiveTask = ValidateAndReceiveAsync(receiveTask, closeBuffer, cancellationToken); + // Wait for whatever receive task we have. We'll then loop around again to re-check our state. + Debug.Assert(receiveTask != null); + await receiveTask.ConfigureAwait(false); } - - // Wait for whatever receive task we have. We'll then loop around again to re-check our state. - Debug.Assert(receiveTask != null); - await receiveTask.ConfigureAwait(false); } - } - finally - { - ArrayPool.Shared.Return(closeBuffer); + finally + { + ArrayPool.Shared.Return(closeBuffer); + } } // We're closed. Close the connection and update the status. @@ -1153,7 +1163,11 @@ private async Task SendCloseFrameAsync(WebSocketCloseStatus closeStatus, string lock (StateUpdateLock) { _sentCloseFrame = true; - if (_state <= WebSocketState.CloseReceived) + if (_receivedCloseFrame && _state < WebSocketState.Closed) + { + _state = WebSocketState.Closed; + } + else if (_state < WebSocketState.CloseSent) { _state = WebSocketState.CloseSent; } diff --git a/src/Common/src/System/Runtime/InteropServices/FunctionWrapper.cs b/src/Common/src/System/Runtime/InteropServices/FunctionWrapper.cs index 9b56ef3de0cb..af0f815ec91b 100644 --- a/src/Common/src/System/Runtime/InteropServices/FunctionWrapper.cs +++ b/src/Common/src/System/Runtime/InteropServices/FunctionWrapper.cs @@ -65,6 +65,7 @@ public static FunctionWrapper Load(IntPtr nativeLibraryHandle, string func return new FunctionLoadResult(FunctionLoadResultKind.LibraryNotFound, null); } +#if netcoreapp20 IntPtr funcPtr = LoadFunctionPointer(nativeLibraryHandle, funcName); if (funcPtr == IntPtr.Zero) { @@ -74,6 +75,11 @@ public static FunctionWrapper Load(IntPtr nativeLibraryHandle, string func { return new FunctionLoadResult(FunctionLoadResultKind.Success, Marshal.GetDelegateForFunctionPointer(funcPtr)); } +#else // use managed NativeLibrary API from .NET Core 3 onwards + return NativeLibrary.TryGetExport(nativeLibraryHandle, funcName, out var funcPtr) + ? new FunctionLoadResult(FunctionLoadResultKind.Success, Marshal.GetDelegateForFunctionPointer(funcPtr)) + : new FunctionLoadResult(FunctionLoadResultKind.FunctionNotFound, null); +#endif }); return new FunctionWrapper(lazyDelegate, libName, funcName); diff --git a/src/Common/tests/System/Net/Configuration.Http.cs b/src/Common/tests/System/Net/Configuration.Http.cs index 178a332af802..1c388fe1b089 100644 --- a/src/Common/tests/System/Net/Configuration.Http.cs +++ b/src/Common/tests/System/Net/Configuration.Http.cs @@ -61,6 +61,7 @@ public static partial class Http public static readonly Uri RemoteVerifyUploadServer = new Uri("http://" + Host + "/" + VerifyUploadHandler); public static readonly Uri SecureRemoteVerifyUploadServer = new Uri("https://" + SecureHost + "/" + VerifyUploadHandler); public static readonly Uri Http2RemoteVerifyUploadServer = new Uri("https://" + Http2Host + "/" + VerifyUploadHandler); + public static readonly Uri[] VerifyUploadServerList = new Uri[] { RemoteVerifyUploadServer, SecureRemoteVerifyUploadServer, Http2RemoteVerifyUploadServer }; public static readonly Uri RemoteEmptyContentServer = new Uri("http://" + Host + "/" + EmptyContentHandler); public static readonly Uri RemoteDeflateServer = new Uri("http://" + Host + "/" + DeflateHandler); diff --git a/src/Common/tests/System/Net/Http/GenericLoopbackServer.cs b/src/Common/tests/System/Net/Http/GenericLoopbackServer.cs index c9858cf1305e..f64460293a79 100644 --- a/src/Common/tests/System/Net/Http/GenericLoopbackServer.cs +++ b/src/Common/tests/System/Net/Http/GenericLoopbackServer.cs @@ -13,14 +13,14 @@ namespace System.Net.Test.Common public abstract class LoopbackServerFactory { - public abstract Task CreateServerAsync(Func funcAsync); + public abstract Task CreateServerAsync(Func funcAsync, int millisecondsTimeout = 30_000); public abstract bool IsHttp11 { get; } public abstract bool IsHttp2 { get; } // Common helper methods - public Task CreateClientAndServerAsync(Func clientFunc, Func serverFunc) + public Task CreateClientAndServerAsync(Func clientFunc, Func serverFunc, int millisecondsTimeout = 30_000) { return CreateServerAsync(async (server, uri) => { @@ -28,7 +28,7 @@ public Task CreateClientAndServerAsync(Func clientFunc, Func ReadRequestHeaderAsync() { // Receive HEADERS frame for request. Frame frame = await ReadFrameAsync(TimeSpan.FromSeconds(30)); + if (frame == null) + { + throw new IOException("Failed to read Headers frame."); + } Assert.Equal(FrameType.Headers, frame.Type); Assert.Equal(FrameFlags.EndHeaders | FrameFlags.EndStream, frame.Flags); return frame.StreamId; @@ -498,6 +502,10 @@ private static int EncodeHeader(HttpHeaderData headerData, Span headerBloc // Receive HEADERS frame for request. Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false); + if (frame == null) + { + throw new IOException("Failed to read Headers frame."); + } Assert.Equal(FrameType.Headers, frame.Type); HeadersFrame headersFrame = (HeadersFrame) frame; @@ -619,7 +627,7 @@ public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory respo throw new Exception("Response body too long"); } - await SendResponseDataAsync(streamId, responseBody, true); + await SendResponseDataAsync(streamId, responseBody, true).ConfigureAwait(false); } public override void Dispose() @@ -637,25 +645,25 @@ public override void Dispose() public override async Task HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList headers = null, string content = null) { - await EstablishConnectionAsync(); + await EstablishConnectionAsync().ConfigureAwait(false); - (int streamId, HttpRequestData requestData) = await ReadAndParseRequestHeaderAsync(); + (int streamId, HttpRequestData requestData) = await ReadAndParseRequestHeaderAsync().ConfigureAwait(false); // We are about to close the connection, after we send the response. // So, send a GOAWAY frame now so the client won't inadvertantly try to reuse the connection. - await SendGoAway(streamId); + await SendGoAway(streamId).ConfigureAwait(false); if (content == null) { - await SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers); + await SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false); } else { - await SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers); - await SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)); + await SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false); + await SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)).ConfigureAwait(false); } - await WaitForConnectionShutdownAsync(); + await WaitForConnectionShutdownAsync().ConfigureAwait(false); return requestData; } @@ -665,7 +673,7 @@ public class Http2Options { public IPAddress Address { get; set; } = IPAddress.Loopback; public int ListenBacklog { get; set; } = 1; - public bool UseSsl { get; set; } = true; + public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn; public SslProtocols SslProtocols { get; set; } = SslProtocols.Tls12; } @@ -673,11 +681,11 @@ public sealed class Http2LoopbackServerFactory : LoopbackServerFactory { public static readonly Http2LoopbackServerFactory Singleton = new Http2LoopbackServerFactory(); - public override async Task CreateServerAsync(Func funcAsync) + public override async Task CreateServerAsync(Func funcAsync, int millisecondsTimeout = 30_000) { using (var server = Http2LoopbackServer.CreateServer()) { - await funcAsync(server, server.Address); + await funcAsync(server, server.Address).TimeoutAfter(millisecondsTimeout).ConfigureAwait(false); } } diff --git a/src/System.Net.Http/tests/FunctionalTests/LoopbackProxyServer.cs b/src/Common/tests/System/Net/Http/LoopbackProxyServer.cs similarity index 99% rename from src/System.Net.Http/tests/FunctionalTests/LoopbackProxyServer.cs rename to src/Common/tests/System/Net/Http/LoopbackProxyServer.cs index 979f3be1f8a8..e278908140fa 100644 --- a/src/System.Net.Http/tests/FunctionalTests/LoopbackProxyServer.cs +++ b/src/Common/tests/System/Net/Http/LoopbackProxyServer.cs @@ -12,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; -namespace System.Net.Http.Functional.Tests +namespace System.Net.Test.Common { /// /// Provides a test-only HTTP proxy. Handles multiple connections/requests and CONNECT tunneling for HTTPS @@ -210,7 +210,7 @@ private async Task ProcessConnectMethod(NetworkStream clientStream, string remot { byte[] buffer = new byte[8000]; int bytesRead; - while ((bytesRead = await clientStream.ReadAsync(buffer)) > 0) + while ((bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await serverStream.WriteAsync(buffer, 0, bytesRead); } @@ -228,7 +228,7 @@ private async Task ProcessConnectMethod(NetworkStream clientStream, string remot { byte[] buffer = new byte[8000]; int bytesRead; - while ((bytesRead = await serverStream.ReadAsync(buffer)) > 0) + while ((bytesRead = await serverStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await clientStream.WriteAsync(buffer, 0, bytesRead); } diff --git a/src/Common/tests/System/Net/Http/LoopbackServer.cs b/src/Common/tests/System/Net/Http/LoopbackServer.cs index 409fd1164d35..a1867eecfc22 100644 --- a/src/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/Common/tests/System/Net/Http/LoopbackServer.cs @@ -565,7 +565,7 @@ public async Task> ReadRequestHeaderAsync() if (line == null) { - throw new Exception("Unexpected EOF trying to read request header"); + throw new IOException("Unexpected EOF trying to read request header"); } return lines; @@ -685,7 +685,7 @@ public sealed class Http11LoopbackServerFactory : LoopbackServerFactory { public static readonly Http11LoopbackServerFactory Singleton = new Http11LoopbackServerFactory(); - public override Task CreateServerAsync(Func funcAsync) + public override Task CreateServerAsync(Func funcAsync, int millisecondsTimeout = 30_000) { return LoopbackServer.CreateServerAsync((server, uri) => funcAsync(server, uri)); } diff --git a/src/Common/tests/Tests/System/StringTests.cs b/src/Common/tests/Tests/System/StringTests.cs index 2e23f798a5d6..4190825940fe 100644 --- a/src/Common/tests/Tests/System/StringTests.cs +++ b/src/Common/tests/Tests/System/StringTests.cs @@ -372,8 +372,6 @@ public static IEnumerable Concat_Objects_TestData() yield return new object[] { new object[] { 1 }, "1" }; yield return new object[] { new object[] { null }, "" }; - // dotnet/coreclr#6785, this will be null for the Concat(object) overload but "" for the object[]/IEnumerable overload - // yield return new object[] { new object[] { new ObjectWithNullToString() }, "" }; yield return new object[] { new object[] { 1, 2 }, "12" }; yield return new object[] { new object[] { null, 1 }, "1" }; @@ -400,6 +398,11 @@ public static IEnumerable Concat_Objects_TestData() // Concat should ignore objects that have a null ToString() value yield return new object[] { new object[] { new ObjectWithNullToString(), "Foo", new ObjectWithNullToString(), "Bar", new ObjectWithNullToString() }, "FooBar" }; + + if (!PlatformDetection.IsFullFramework) + { + yield return new object[] { new object[] { new ObjectWithNullToString() }, "" }; + } } [Theory] diff --git a/src/CoreFx.Private.TestUtilities/ref/CoreFx.Private.TestUtilities.cs b/src/CoreFx.Private.TestUtilities/ref/CoreFx.Private.TestUtilities.cs index fc0573f49fc2..00a65a93bfab 100644 --- a/src/CoreFx.Private.TestUtilities/ref/CoreFx.Private.TestUtilities.cs +++ b/src/CoreFx.Private.TestUtilities/ref/CoreFx.Private.TestUtilities.cs @@ -111,6 +111,7 @@ public static partial class PlatformDetection public static bool IsWindows10Version1703OrGreater { get { throw null; } } public static bool IsWindows10Version1709OrGreater { get { throw null; } } public static bool IsWindows10Version1803OrGreater { get { throw null; } } + public static bool IsWindows10Version1903OrGreater { get { throw null; } } public static bool IsWindows7 { get { throw null; } } public static bool IsWindows8x { get { throw null; } } public static bool IsWindows8xOrLater { get { throw null; } } diff --git a/src/CoreFx.Private.TestUtilities/src/Configurations.props b/src/CoreFx.Private.TestUtilities/src/Configurations.props index 80a36c009e40..627123a3f65c 100644 --- a/src/CoreFx.Private.TestUtilities/src/Configurations.props +++ b/src/CoreFx.Private.TestUtilities/src/Configurations.props @@ -9,8 +9,6 @@ netcoreapp-Unix; netcoreapp2.0-Windows_NT; netcoreapp2.0-Unix; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; diff --git a/src/CoreFx.Private.TestUtilities/src/CoreFx.Private.TestUtilities.csproj b/src/CoreFx.Private.TestUtilities/src/CoreFx.Private.TestUtilities.csproj index 77a9ff527e53..1112b71aae4a 100644 --- a/src/CoreFx.Private.TestUtilities/src/CoreFx.Private.TestUtilities.csproj +++ b/src/CoreFx.Private.TestUtilities/src/CoreFx.Private.TestUtilities.csproj @@ -10,7 +10,7 @@ false $(NoWarn);CS3021 Test Utilities are not supported on this platform - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreapp2.0-Unix-Debug;netcoreapp2.0-Unix-Release;netcoreapp2.0-Windows_NT-Debug;netcoreapp2.0-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreapp2.0-Unix-Debug;netcoreapp2.0-Unix-Release;netcoreapp2.0-Windows_NT-Debug;netcoreapp2.0-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard-Debug;netstandard-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Unix.cs b/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Unix.cs index 8421ba4e7269..541b4177a233 100644 --- a/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Unix.cs +++ b/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Unix.cs @@ -23,6 +23,7 @@ public static partial class PlatformDetection public static bool IsWindows10Version1703OrGreater => false; public static bool IsWindows10Version1709OrGreater => false; public static bool IsWindows10Version1803OrGreater => false; + public static bool IsWindows10Version1903OrGreater => false; public static bool IsNotOneCoreUAP => true; public static bool IsInAppContainer => false; public static int WindowsVersion => -1; @@ -64,20 +65,20 @@ public static partial class PlatformDetection public static bool IsDrawingSupported { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) -#if netcoreapp30 - ? NativeLibrary.TryLoad("libgdiplus.dylib", out _) - : NativeLibrary.TryLoad("libgdiplus.so", out _) || NativeLibrary.TryLoad("libgdiplus.so.0", out _); -#else +#if netcoreapp20 ? dlopen("libgdiplus.dylib", RTLD_LAZY) != IntPtr.Zero : dlopen("libgdiplus.so", RTLD_LAZY) != IntPtr.Zero || dlopen("libgdiplus.so.0", RTLD_LAZY) != IntPtr.Zero; - public static bool IsInContainer => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists("/.dockerenv"); - [DllImport("libdl")] private static extern IntPtr dlopen(string libName, int flags); private const int RTLD_LAZY = 0x001; +#else // use managed NativeLibrary API from .NET Core 3 onwards + ? NativeLibrary.TryLoad("libgdiplus.dylib", out _) + : NativeLibrary.TryLoad("libgdiplus.so", out _) || NativeLibrary.TryLoad("libgdiplus.so.0", out _); #endif + public static bool IsInContainer => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && File.Exists("/.dockerenv"); + public static bool IsSoundPlaySupported { get; } = false; public static Version OSXVersion { get; } = ToVersion(PlatformApis.GetOSVersion()); diff --git a/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs b/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs index 194509a72e25..10f0cf741728 100644 --- a/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs +++ b/src/CoreFx.Private.TestUtilities/src/System/PlatformDetection.Windows.cs @@ -66,6 +66,10 @@ public static partial class PlatformDetection public static bool IsWindows10Version1803OrGreater => GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 17134; + // >= Windows 10 May 2019 Update (19H1) + public static bool IsWindows10Version1903OrGreater => + GetWindowsVersion() == 10 && GetWindowsMinorVersion() == 0 && GetWindowsBuildNumber() >= 18362; + // Windows OneCoreUAP SKU doesn't have httpapi.dll public static bool IsNotOneCoreUAP => File.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "httpapi.dll")); diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7063f069799d..efcca891dd45 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,6 +2,7 @@ false + true true true diff --git a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/ManifestEtw.cs b/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/ManifestEtw.cs deleted file mode 100644 index abc4933c1108..000000000000 --- a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/ManifestEtw.cs +++ /dev/null @@ -1,216 +0,0 @@ -// 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. - -namespace Microsoft.Win32 -{ - using Microsoft.Win32.SafeHandles; - using System; -#if ES_BUILD_STANDALONE - using Microsoft.Diagnostics.Tracing; -#else - using System.Diagnostics.Tracing; -#endif - using System.Runtime.InteropServices; - using System.Security; - using System.Text; - - [SuppressUnmanagedCodeSecurityAttribute()] - internal static partial class UnsafeNativeMethods - { - [SuppressUnmanagedCodeSecurityAttribute()] - internal static unsafe class ManifestEtw - { -// Disable warning about unused fields as these structures are defined by the corresponding Windows APIs. -#pragma warning disable CS0649 - // - // ETW Library - // - private const string ADVAPI32 = "advapi32.dll"; - - // - // Constants error coded returned by ETW APIs - // - - // The event size is larger than the allowed maximum (64k - header). - internal const int ERROR_ARITHMETIC_OVERFLOW = 534; - - // Occurs when filled buffers are trying to flush to disk, - // but disk IOs are not happening fast enough. - // This happens when the disk is slow and event traffic is heavy. - // Eventually, there are no more free (empty) buffers and the event is dropped. - internal const int ERROR_NOT_ENOUGH_MEMORY = 8; - - internal const int ERROR_MORE_DATA = 0xEA; - internal const int ERROR_NOT_SUPPORTED = 50; - internal const int ERROR_INVALID_PARAMETER = 0x57; - - // - // ETW Methods - // - - internal const int EVENT_CONTROL_CODE_DISABLE_PROVIDER = 0; - internal const int EVENT_CONTROL_CODE_ENABLE_PROVIDER = 1; - internal const int EVENT_CONTROL_CODE_CAPTURE_STATE = 2; - - // - // Callback - // - internal unsafe delegate void EtwEnableCallback( - [In] ref Guid sourceId, - [In] int isEnabled, - [In] byte level, - [In] long matchAnyKeywords, - [In] long matchAllKeywords, - [In] EVENT_FILTER_DESCRIPTOR* filterData, - [In] void* callbackContext - ); - - // - // Registration APIs - // - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventRegister", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - internal static extern unsafe uint EventRegister( - [In] ref Guid providerId, - [In]EtwEnableCallback enableCallback, - [In]void* callbackContext, - [In][Out]ref long registrationHandle - ); - - // - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventUnregister", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - internal static extern uint EventUnregister([In] long registrationHandle); - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventWriteString", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - internal static extern unsafe int EventWriteString( - [In] long registrationHandle, - [In] byte level, - [In] long keyword, - [In] string msg - ); - - [StructLayout(LayoutKind.Sequential)] - unsafe internal struct EVENT_FILTER_DESCRIPTOR - { - public long Ptr; - public int Size; - public int Type; - }; - - /// - /// Call the ETW native API EventWriteTransfer and checks for invalid argument error. - /// The implementation of EventWriteTransfer on some older OSes (Windows 2008) does not accept null relatedActivityId. - /// So, for these cases we will retry the call with an empty Guid. - /// - internal static int EventWriteTransferWrapper(long registrationHandle, - ref EventDescriptor eventDescriptor, - Guid* activityId, - Guid* relatedActivityId, - int userDataCount, - EventProvider.EventData* userData) - { - int HResult = EventWriteTransfer(registrationHandle, ref eventDescriptor, activityId, relatedActivityId, userDataCount, userData); - if (HResult == ERROR_INVALID_PARAMETER && relatedActivityId == null) - { - Guid emptyGuid = Guid.Empty; - HResult = EventWriteTransfer(registrationHandle, ref eventDescriptor, activityId, &emptyGuid, userDataCount, userData); - } - - return HResult; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventWriteTransfer", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - [SuppressUnmanagedCodeSecurityAttribute] // Don't do security checks - private static extern int EventWriteTransfer( - [In] long registrationHandle, - [In] ref EventDescriptor eventDescriptor, - [In] Guid* activityId, - [In] Guid* relatedActivityId, - [In] int userDataCount, - [In] EventProvider.EventData* userData - ); - - internal enum ActivityControl : uint - { - EVENT_ACTIVITY_CTRL_GET_ID = 1, - EVENT_ACTIVITY_CTRL_SET_ID = 2, - EVENT_ACTIVITY_CTRL_CREATE_ID = 3, - EVENT_ACTIVITY_CTRL_GET_SET_ID = 4, - EVENT_ACTIVITY_CTRL_CREATE_SET_ID = 5 - }; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventActivityIdControl", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - [SuppressUnmanagedCodeSecurityAttribute] // Don't do security checks - internal static extern int EventActivityIdControl([In] ActivityControl ControlCode, [In][Out] ref Guid ActivityId); - - internal enum EVENT_INFO_CLASS - { - BinaryTrackInfo, - SetEnableAllKeywords, - SetTraits, - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EventSetInformation", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - [SuppressUnmanagedCodeSecurityAttribute] // Don't do security checks - internal static extern int EventSetInformation( - [In] long registrationHandle, - [In] EVENT_INFO_CLASS informationClass, - [In] void* eventInformation, - [In] int informationLength); - - // Support for EnumerateTraceGuidsEx - internal enum TRACE_QUERY_INFO_CLASS - { - TraceGuidQueryList, - TraceGuidQueryInfo, - TraceGuidQueryProcess, - TraceStackTracingInfo, - MaxTraceSetInfoClass - }; - - internal struct TRACE_GUID_INFO - { - public int InstanceCount; - public int Reserved; - }; - - internal struct TRACE_PROVIDER_INSTANCE_INFO - { - public int NextOffset; - public int EnableCount; - public int Pid; - public int Flags; - }; - - internal struct TRACE_ENABLE_INFO - { - public int IsEnabled; - public byte Level; - public byte Reserved1; - public ushort LoggerId; - public int EnableProperty; - public int Reserved2; - public long MatchAnyKeyword; - public long MatchAllKeyword; - }; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] - [DllImport(ADVAPI32, ExactSpelling = true, EntryPoint = "EnumerateTraceGuidsEx", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] - [SuppressUnmanagedCodeSecurityAttribute] // Don't do security checks - internal static extern int EnumerateTraceGuidsEx( - TRACE_QUERY_INFO_CLASS TraceQueryInfoClass, - void* InBuffer, - int InBufferSize, - void* OutBuffer, - int OutBufferSize, - ref int ReturnLength); - -#pragma warning restore CS0649 - } - } -} diff --git a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj b/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj index 153b6879d209..4507b8a0da2e 100644 --- a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj +++ b/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/Microsoft.Diagnostics.Tracing.EventSource.Redist.csproj @@ -12,7 +12,18 @@ - + + + + + + + + + + + + diff --git a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/RuntimeSpecific.cs b/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/RuntimeSpecific.cs deleted file mode 100644 index cc14f9b7062b..000000000000 --- a/src/Microsoft.Diagnostics.Tracing.EventSource.Redist/src/RuntimeSpecific.cs +++ /dev/null @@ -1,245 +0,0 @@ -// 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.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.Reflection; -using Microsoft.Win32; - -#if ES_BUILD_STANDALONE -namespace Microsoft.Diagnostics.Tracing -#else -namespace System.Diagnostics.Tracing -#endif -{ - public partial class EventSource - { -#if FEATURE_MANAGED_ETW && FEATURE_PERFTRACING - // For non-Windows, we use a thread-local variable to hold the activity ID. - // On Windows, ETW has it's own thread-local variable and we participate in its use. - [ThreadStatic] - private static Guid s_currentThreadActivityId; -#endif // FEATURE_MANAGED_ETW && FEATURE_PERFTRACING - - // ActivityID support (see also WriteEventWithRelatedActivityIdCore) - /// - /// When a thread starts work that is on behalf of 'something else' (typically another - /// thread or network request) it should mark the thread as working on that other work. - /// This API marks the current thread as working on activity 'activityID'. This API - /// should be used when the caller knows the thread's current activity (the one being - /// overwritten) has completed. Otherwise, callers should prefer the overload that - /// return the oldActivityThatWillContinue (below). - /// - /// All events created with the EventSource on this thread are also tagged with the - /// activity ID of the thread. - /// - /// It is common, and good practice after setting the thread to an activity to log an event - /// with a 'start' opcode to indicate that precise time/thread where the new activity - /// started. - /// - /// A Guid that represents the new activity with which to mark - /// the current thread - public static void SetCurrentThreadActivityId(Guid activityId) - { - if (TplEventSource.Log != null) - TplEventSource.Log.SetActivityId(activityId); -#if FEATURE_MANAGED_ETW -#if FEATURE_ACTIVITYSAMPLING - Guid newId = activityId; -#endif // FEATURE_ACTIVITYSAMPLING - // We ignore errors to keep with the convention that EventSources do not throw errors. - // Note we can't access m_throwOnWrites because this is a static method. - -#if FEATURE_PERFTRACING - s_currentThreadActivityId = activityId; -#elif PLATFORM_WINDOWS - if (UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( - UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, - ref activityId) == 0) -#endif // FEATURE_PERFTRACING - { -#if FEATURE_ACTIVITYSAMPLING - var activityDying = s_activityDying; - if (activityDying != null && newId != activityId) - { - if (activityId == Guid.Empty) - { - activityId = FallbackActivityId; - } - // OutputDebugString(string.Format("Activity dying: {0} -> {1}", activityId, newId)); - activityDying(activityId); // This is actually the OLD activity ID. - } -#endif // FEATURE_ACTIVITYSAMPLING - } -#endif // FEATURE_MANAGED_ETW - } - - /// - /// When a thread starts work that is on behalf of 'something else' (typically another - /// thread or network request) it should mark the thread as working on that other work. - /// This API marks the current thread as working on activity 'activityID'. It returns - /// whatever activity the thread was previously marked with. There is a convention that - /// callers can assume that callees restore this activity mark before the callee returns. - /// To encourage this, this API returns the old activity, so that it can be restored later. - /// - /// All events created with the EventSource on this thread are also tagged with the - /// activity ID of the thread. - /// - /// It is common, and good practice after setting the thread to an activity to log an event - /// with a 'start' opcode to indicate that precise time/thread where the new activity - /// started. - /// - /// A Guid that represents the new activity with which to mark - /// the current thread - /// The Guid that represents the current activity - /// which will continue at some point in the future, on the current thread - public static void SetCurrentThreadActivityId(Guid activityId, out Guid oldActivityThatWillContinue) - { - oldActivityThatWillContinue = activityId; -#if FEATURE_MANAGED_ETW - // We ignore errors to keep with the convention that EventSources do not throw errors. - // Note we can't access m_throwOnWrites because this is a static method. - -#if FEATURE_PERFTRACING - oldActivityThatWillContinue = s_currentThreadActivityId; - s_currentThreadActivityId = activityId; -#elif PLATFORM_WINDOWS - UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( - UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_SET_ID, - ref oldActivityThatWillContinue); -#endif // FEATURE_PERFTRACING -#endif // FEATURE_MANAGED_ETW - - // We don't call the activityDying callback here because the caller has declared that - // it is not dying. - if (TplEventSource.Log != null) - TplEventSource.Log.SetActivityId(activityId); - } - - /// - /// Retrieves the ETW activity ID associated with the current thread. - /// - public static Guid CurrentThreadActivityId - { - get - { - // We ignore errors to keep with the convention that EventSources do not throw - // errors. Note we can't access m_throwOnWrites because this is a static method. - Guid retVal = new Guid(); -#if FEATURE_MANAGED_ETW -#if FEATURE_PERFTRACING - retVal = s_currentThreadActivityId; -#elif PLATFORM_WINDOWS - UnsafeNativeMethods.ManifestEtw.EventActivityIdControl( - UnsafeNativeMethods.ManifestEtw.ActivityControl.EVENT_ACTIVITY_CTRL_GET_ID, - ref retVal); -#endif // FEATURE_PERFTRACING -#endif // FEATURE_MANAGED_ETW - return retVal; - } - } - - private int GetParameterCount(EventMetadata eventData) - { - return eventData.Parameters.Length; - } - - private Type GetDataType(EventMetadata eventData, int parameterId) - { - return eventData.Parameters[parameterId].ParameterType; - } - - private static string GetResourceString(string key, params object[] args) - { - return string.Format(Resources.GetResourceString(key), args); - } - - private static readonly bool m_EventSourcePreventRecursion = false; - } - - internal partial class ManifestBuilder - { - private string GetTypeNameHelper(Type type) - { - switch (type.GetTypeCode()) - { - case TypeCode.Boolean: - return "win:Boolean"; - case TypeCode.Byte: - return "win:UInt8"; - case TypeCode.Char: - case TypeCode.UInt16: - return "win:UInt16"; - case TypeCode.UInt32: - return "win:UInt32"; - case TypeCode.UInt64: - return "win:UInt64"; - case TypeCode.SByte: - return "win:Int8"; - case TypeCode.Int16: - return "win:Int16"; - case TypeCode.Int32: - return "win:Int32"; - case TypeCode.Int64: - return "win:Int64"; - case TypeCode.String: - return "win:UnicodeString"; - case TypeCode.Single: - return "win:Float"; - case TypeCode.Double: - return "win:Double"; - case TypeCode.DateTime: - return "win:FILETIME"; - default: - if (type == typeof(Guid)) - return "win:GUID"; - else if (type == typeof(IntPtr)) - return "win:Pointer"; - else if ((type.IsArray || type.IsPointer) && type.GetElementType() == typeof(byte)) - return "win:Binary"; - - ManifestError(SR.Format(SR.EventSource_UnsupportedEventTypeInManifest, type.Name), true); - return string.Empty; - } - } - } - - internal partial class EventProvider - { - internal unsafe int SetInformation( - UnsafeNativeMethods.ManifestEtw.EVENT_INFO_CLASS eventInfoClass, - IntPtr data, - uint dataSize) - { - int status = UnsafeNativeMethods.ManifestEtw.ERROR_NOT_SUPPORTED; - - if (!m_setInformationMissing) - { - try - { - status = UnsafeNativeMethods.ManifestEtw.EventSetInformation( - m_regHandle, - eventInfoClass, - (void*)data, - (int)dataSize); - } - catch (TypeLoadException) - { - m_setInformationMissing = true; - } - } - - return status; - } - } - - internal static class Resources - { - internal static string GetResourceString(string key, params object[] args) - { - return string.Format(Resources.GetResourceString(key), args); - } - } -} diff --git a/src/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj b/src/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj index feecce9d2f4c..295b77742ace 100644 --- a/src/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj +++ b/src/Microsoft.IO.Redist/src/Microsoft.IO.Redist.csproj @@ -52,6 +52,9 @@ Common\Interop\Windows\Interop.UNICODE_STRING.cs + + Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + Common\Interop\Windows\Interop.BOOLEAN.cs diff --git a/src/Microsoft.VisualBasic.Core/Microsoft.VisualBasic.Core.sln b/src/Microsoft.VisualBasic.Core/Microsoft.VisualBasic.Core.sln index a8ffe1fb7465..242e246f52ab 100644 --- a/src/Microsoft.VisualBasic.Core/Microsoft.VisualBasic.Core.sln +++ b/src/Microsoft.VisualBasic.Core/Microsoft.VisualBasic.Core.sln @@ -1,13 +1,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27213.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28805.205 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualBasic.Core.Tests", "tests\Microsoft.VisualBasic.Core.Tests.csproj", "{325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}" ProjectSection(ProjectDependencies) = postProject {A32671B6-5470-4F9C-9CD8-4094B9AB0799} = {A32671B6-5470-4F9C-9CD8-4094B9AB0799} EndProjectSection EndProject -Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.VisualBasic.Core.VB.Tests", "tests\VB\Microsoft.VisualBasic.VB.Core.Tests.vbproj", "{DA95454E-5F0E-4F8A-871A-25D1AB62867A}" +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.VisualBasic.VB.Core.Tests", "tests\VB\Microsoft.VisualBasic.VB.Core.Tests.vbproj", "{DA95454E-5F0E-4F8A-871A-25D1AB62867A}" ProjectSection(ProjectDependencies) = postProject {A32671B6-5470-4F9C-9CD8-4094B9AB0799} = {A32671B6-5470-4F9C-9CD8-4094B9AB0799} EndProjectSection @@ -29,24 +29,42 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Windows_NT-Debug|Any CPU = Windows_NT-Debug|Any CPU + Windows_NT-Release|Any CPU = Windows_NT-Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU - {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU - {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU - {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU - {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU - {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU - {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU - {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU - {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU - {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU - {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU - {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU - {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU - {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Windows_NT-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Windows_NT-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Windows_NT-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {325260D6-D5DD-4E06-9DA2-9AF2AD9DE8C8}.Windows_NT-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Windows_NT-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Windows_NT-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Windows_NT-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {DA95454E-5F0E-4F8A-871A-25D1AB62867A}.Windows_NT-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Windows_NT-Debug|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Windows_NT-Debug|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Windows_NT-Release|Any CPU.ActiveCfg = netcoreapp-Windows_NT-Release|Any CPU + {A32671B6-5470-4F9C-9CD8-4094B9AB0799}.Windows_NT-Release|Any CPU.Build.0 = netcoreapp-Windows_NT-Release|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Windows_NT-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Windows_NT-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Windows_NT-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU + {82A4357C-0A9F-4970-AAEA-216A73D8A73E}.Windows_NT-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs b/src/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs index a97b125b906b..e46a95a0d1d1 100644 --- a/src/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs +++ b/src/Microsoft.VisualBasic.Core/ref/Microsoft.VisualBasic.Core.cs @@ -100,6 +100,17 @@ internal DateAndTime() { } public static System.DateTime Now { get { throw null; } } public static System.DateTime Today { get { throw null; } } } + public sealed partial class ErrObject + { + internal ErrObject() { } + public void Clear() { } + public string Description { get { throw null; } set { } } + public int Erl { get { throw null; } } + public System.Exception GetException() { throw null; } + public int LastDllError { get { throw null; } } + public int Number { get { throw null; } set { } } + public void Raise(int Number, object Source = null, object Description = null, object HelpFile = null, object HelpContext = null) { } + } [System.AttributeUsageAttribute(System.AttributeTargets.Class, AllowMultiple=false, Inherited=false)] [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] public sealed partial class HideModuleNameAttribute : System.Attribute @@ -110,6 +121,7 @@ public HideModuleNameAttribute() { } public sealed partial class Information { internal Information() { } + public static ErrObject Err() { throw null; } public static bool IsArray(object VarName) { throw null; } public static bool IsDate(object Expression) { throw null; } public static bool IsDBNull(object Expression) { throw null; } @@ -467,6 +479,7 @@ public sealed partial class ProjectData { internal ProjectData() { } public static void ClearProjectError() { } + public static System.Exception CreateProjectError(int hr) { throw null; } public static void SetProjectError(System.Exception ex) { } public static void SetProjectError(System.Exception ex, int lErl) { } } diff --git a/src/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj b/src/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj index 7cbc84d9def7..8c74e8644520 100644 --- a/src/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj +++ b/src/Microsoft.VisualBasic.Core/src/Microsoft.VisualBasic.Core.vbproj @@ -68,6 +68,7 @@ + diff --git a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ExceptionUtils.vb b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ExceptionUtils.vb index 7ec627e0cca0..653334b82245 100644 --- a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ExceptionUtils.vb +++ b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ExceptionUtils.vb @@ -190,8 +190,7 @@ Namespace Microsoft.VisualBasic.CompilerServices End Function Friend Shared Function VbMakeException(ByVal ex As Exception, ByVal hr As Integer) As System.Exception - ' UNDONE - Err() requires port of Information.vb, ProjectData.vb - 'Err().SetUnmappedError(hr) + Err().SetUnmappedError(hr) Return ex End Function diff --git a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ProjectData.vb b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ProjectData.vb index d81f9d05030f..3587e998538f 100644 --- a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ProjectData.vb +++ b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/CompilerServices/ProjectData.vb @@ -2,11 +2,14 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System + Namespace Global.Microsoft.VisualBasic.CompilerServices Public NotInheritable Class ProjectData + Friend m_Err As ErrObject Friend m_rndSeed As Integer = &H50000I 'm_oProject is per-Thread @@ -14,12 +17,6 @@ Namespace Global.Microsoft.VisualBasic.CompilerServices Private Sub New() End Sub - Public Overloads Shared Sub SetProjectError(ex As Global.System.Exception) - End Sub - Public Overloads Shared Sub SetProjectError(ex As Global.System.Exception, lErl As Integer) - End Sub - Public Shared Sub ClearProjectError() - End Sub Friend Shared Function GetProjectData() As ProjectData '************************* @@ -34,5 +31,61 @@ Namespace Global.Microsoft.VisualBasic.CompilerServices m_oProject = GetProjectData End If End Function + + ''' + ''' This function is called by the compiler in response to err code, e.g. err 123 + ''' It is also called when the compiler encounters a resume that isn't preceded by an On Error command + ''' + ''' + ''' + Public Shared Function CreateProjectError(ByVal hr As Integer) As System.Exception + '************************* + '*** PERFORMANCE NOTE: *** + '************************* + ' Err Object is and is pretty expensive to access so we cache to a local to cut the number of accesses + Dim ErrObj As ErrObject = Err() + ErrObj.Clear() + Dim ErrNumber As Integer = ErrObj.MapErrorNumber(hr) + Return ErrObj.CreateException(hr, Utils.GetResourceString(CType(ErrNumber, vbErrors))) + End Function + + ''' + ''' Called by the compiler in response to falling into a catch block. + ''' Inside the catch statement the compiler generates code to call: + ''' ProjectData::SetProjectError(exception) That call + ''' in turns sets the ErrObject which is accessed via the VB Err statement. + ''' So a VB6 programmer would typically then do something like: + ''' if err.Number = * do something where err accesses the ErrObject that + ''' is set by this method. + ''' + ''' + Public Overloads Shared Sub SetProjectError(ByVal ex As Exception) + Err.CaptureException(ex) + End Sub + + ''' + ''' Called by the compiler in response to falling into a catch block. + ''' Inside the catch statement the compiler generates code to call: + ''' ProjectData::SetProjectError(exception, lineNumber) This call + ''' differs from SetProjectError(ex as Exception)because it is called + ''' when the exception is thrown from a specific line number, e.g: + ''' 123: Throw new Exception + ''' 123: Error x80004003 + ''' This method in turn sets the ErrObject which is accessed via the + ''' VB "Err" statement. + ''' So a VB6 programmer could then do something like: + ''' if err.Number = * + ''' err.Erl will also be set + ''' is set by this class. + ''' + ''' + ''' + Public Overloads Shared Sub SetProjectError(ByVal ex As Exception, ByVal lErl As Integer) + Err.CaptureException(ex, lErl) + End Sub + + Public Shared Sub ClearProjectError() + Err.Clear() + End Sub End Class End Namespace diff --git a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/ErrObject.vb b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/ErrObject.vb new file mode 100644 index 000000000000..c50742f607d3 --- /dev/null +++ b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/ErrObject.vb @@ -0,0 +1,294 @@ +' 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. + +Imports Microsoft.VisualBasic.CompilerServices +Imports Microsoft.VisualBasic.CompilerServices.Utils +Imports Microsoft.VisualBasic.CompilerServices.ExceptionUtils + +Imports System +Imports System.Runtime.InteropServices + +Namespace Microsoft.VisualBasic + + Public NotInheritable Class ErrObject + + ' Error object private values + Private m_curException As Exception + Private m_curErl As Integer + Private m_curNumber As Integer + Private m_curDescription As String + Private m_NumberIsSet As Boolean + Private m_ClearOnCapture As Boolean + Private m_DescriptionIsSet As Boolean + + Friend Sub New() + Me.Clear() 'need to do this so the fields are set to Empty string, not Nothing + End Sub + + '============================================================================ + ' ErrObject functions. + '============================================================================ + Public ReadOnly Property Erl() As Integer + Get + Return m_curErl + End Get + End Property + + Public Property Number() As Integer + Get + If m_NumberIsSet Then + Return m_curNumber + End If + + If Not m_curException Is Nothing Then + Me.Number = MapExceptionToNumber(m_curException) + Return m_curNumber + Else + 'The default case. NOTE: falling into the default does not "Set" the property. + 'We only get here if the Err object was previously cleared. + Return 0 + End If + End Get + + Set(ByVal Value As Integer) + m_curNumber = MapErrorNumber(Value) + m_NumberIsSet = True + End Set + End Property + + ''' + ''' Determines what the correct error description should be. + ''' If we don't have an exception that we are responding to then + ''' we don't do anything to the message. + ''' If we do have an exception pending, we morph the description + ''' to match the corresponding VB error. + ''' We also special case HRESULT exceptions to map to a VB description + ''' if we have one. + ''' + ''' + ''' + Private Function FilterDefaultMessage(ByVal Msg As String) As String + Dim NewMsg As String + + 'This is one of the default messages, + If m_curException Is Nothing Then + 'Leave message as is + Return Msg + End If + + Dim tmpNumber As Integer = Me.Number + + If Msg Is Nothing OrElse Msg.Length = 0 Then + Msg = GetResourceString("ID" & CStr(tmpNumber)) + ElseIf System.String.CompareOrdinal("Exception from HRESULT: 0x", 0, Msg, 0, Math.Min(Msg.Length, 26)) = 0 Then + NewMsg = GetResourceString("ID" & CStr(m_curNumber)) + If Not NewMsg Is Nothing Then + Msg = NewMsg + End If + End If + + Return Msg + End Function + + Public Property Description() As String + Get + If m_DescriptionIsSet Then + Return m_curDescription + End If + + If Not m_curException Is Nothing Then + Me.Description = FilterDefaultMessage(m_curException.Message) + Return m_curDescription + Else + 'The default case. NOTE: falling into the default does not "Set" the property. + 'We only get here if the Err object was previously cleared. + Return "" + End If + End Get + + Set(ByVal Value As String) + m_curDescription = Value + m_DescriptionIsSet = True + End Set + End Property + + Public Function GetException() As Exception + Return m_curException + End Function + + ''' + ''' VB calls clear whenever it executes any type of Resume statement, Exit Sub, Exit funcion, exit Property, or + ''' any On Error statement. + ''' + Public Sub Clear() + 'CONSIDER: do we even care about CLEARING the fields if clearing the flags are enough (aside from m_curException)? + m_curException = Nothing + m_curNumber = 0 + m_curDescription = "" + m_curErl = 0 + m_NumberIsSet = False + m_DescriptionIsSet = False + m_ClearOnCapture = True + End Sub + + ''' + ''' This function is called when the Raise code command is executed + ''' + ''' The error code being raised + ''' If not supplied we take the name from the assembly + ''' If not supplied, we try to look one up based on the error code being raised + ''' + ''' + Public Sub Raise(ByVal Number As Integer, + Optional ByVal Source As Object = Nothing, + Optional ByVal Description As Object = Nothing, + Optional ByVal HelpFile As Object = Nothing, + Optional ByVal HelpContext As Object = Nothing) + + If Number = 0 Then + 'This is only called by Raise, so Raise(0) should give the following exception + Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Number")) + End If + Me.Number = Number + + If Not Description Is Nothing Then + Me.Description = CStr(Description) + ElseIf Not m_DescriptionIsSet Then + 'Set the Description here so the exception object contains the right message + Me.Description = GetResourceString(CType(m_curNumber, vbErrors)) + End If + + Dim e As Exception + e = MapNumberToException(m_curNumber, m_curDescription) + m_ClearOnCapture = False + Throw e + End Sub + + ReadOnly Property LastDllError() As Integer + Get + Return Marshal.GetLastWin32Error() + End Get + End Property + + Friend Sub SetUnmappedError(ByVal Number As Integer) + Me.Clear() + Me.Number = Number + m_ClearOnCapture = False + End Sub + + 'a function like this that can be used by the runtime to generate errors which will also do a clear would be nice. + Friend Function CreateException(ByVal Number As Integer, ByVal Description As String) As System.Exception + Me.Clear() + Me.Number = Number + + If Number = 0 Then + 'This is only called by Error xxxx, zero is not a valid exception number + Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1, "Number")) + End If + + Dim e As Exception = MapNumberToException(m_curNumber, Description) + m_ClearOnCapture = False + Return e + End Function + + Friend Overloads Sub CaptureException(ByVal ex As Exception) + 'if we've already captured this exception, then we're done + If ex IsNot m_curException Then + If m_ClearOnCapture Then + Me.Clear() + Else + m_ClearOnCapture = True 'False only used once - set this flag back to the default + End If + m_curException = ex + End If + End Sub + + Friend Overloads Sub CaptureException(ByVal ex As Exception, ByVal lErl As Integer) + CaptureException(ex) + m_curErl = lErl 'This is the only place where the line number can be set + End Sub + + Private Function MapExceptionToNumber(ByVal e As Exception) As Integer + Diagnostics.Debug.Assert(e IsNot Nothing, "Exception shouldn't be Nothing") + Dim typ As Type = e.GetType() + + If typ Is GetType(System.IndexOutOfRangeException) Then + Return vbErrors.OutOfBounds + ElseIf typ Is GetType(System.RankException) Then + Return vbErrors.OutOfBounds + ElseIf typ Is GetType(System.DivideByZeroException) Then + Return vbErrors.DivByZero + ElseIf typ Is GetType(System.OverflowException) Then + Return vbErrors.Overflow + ElseIf typ Is GetType(System.NotFiniteNumberException) Then + Dim exNotFiniteNumber As NotFiniteNumberException = CType(e, NotFiniteNumberException) + If exNotFiniteNumber.OffendingNumber = 0 Then + Return vbErrors.DivByZero + Else + Return vbErrors.Overflow + End If + ElseIf typ Is GetType(System.NullReferenceException) Then + Return vbErrors.ObjNotSet + ElseIf TypeOf e Is System.AccessViolationException Then + Return vbErrors.AccessViolation + ElseIf typ Is GetType(System.InvalidCastException) Then + Return vbErrors.TypeMismatch + ElseIf typ Is GetType(System.NotSupportedException) Then + Return vbErrors.TypeMismatch + ElseIf typ Is GetType(System.Runtime.InteropServices.SEHException) Then + Return vbErrors.DLLCallException + ElseIf typ Is GetType(System.DllNotFoundException) Then + Return vbErrors.FileNotFound + ElseIf typ Is GetType(System.EntryPointNotFoundException) Then + Return vbErrors.InvalidDllFunctionName + ' + 'Must fall after EntryPointNotFoundException because of inheritance + ' + ElseIf typ Is GetType(System.TypeLoadException) Then + Return vbErrors.CantCreateObject + ElseIf typ Is GetType(System.OutOfMemoryException) Then + Return vbErrors.OutOfMemory + ElseIf typ Is GetType(System.FormatException) Then + Return vbErrors.TypeMismatch + ElseIf typ Is GetType(System.IO.DirectoryNotFoundException) Then + Return vbErrors.PathNotFound + ElseIf typ Is GetType(System.IO.IOException) Then + Return vbErrors.IOError + ElseIf typ Is GetType(System.IO.FileNotFoundException) Then + Return vbErrors.FileNotFound + ElseIf TypeOf e Is MissingMemberException Then + Return vbErrors.OLENoPropOrMethod + ElseIf TypeOf e Is Runtime.InteropServices.InvalidOleVariantTypeException Then + Return vbErrors.InvalidTypeLibVariable + Else + Return vbErrors.IllegalFuncCall 'Generic error + End If + + End Function + + Private Function MapNumberToException(ByVal Number As Integer, + ByVal Description As String) As System.Exception + Return ExceptionUtils.BuildException(Number, Description, False) + End Function + + Friend Function MapErrorNumber(ByVal Number As Integer) As Integer + If Number > 65535 Then + ' Number cannot be greater than 65535. + Throw New ArgumentException(GetResourceString(SR.Argument_InvalidValue1), "Number") + End If + + If Number >= 0 Then + Return Number + End If + + 'strip off top two bytes if FACILITY_CONTROL is set + If (Number And SCODE_FACILITY) = FACILITY_CONTROL Then + Return (Number And &HFFFFI) + End If + + Return Number + End Function + + End Class +End Namespace diff --git a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Information.vb b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Information.vb index 715fb30bb98d..78d18821366e 100644 --- a/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Information.vb +++ b/src/Microsoft.VisualBasic.Core/src/Microsoft/VisualBasic/Information.vb @@ -37,6 +37,21 @@ Namespace Microsoft.VisualBasic &HFF00FFI, &HFFFFI, &HFFFFFFI} Friend Const COMObjectName As String = "__ComObject" + '============================================================================ + ' Error functions. + '============================================================================ + Public Function Err() As ErrObject + + Dim oProj As ProjectData + oProj = ProjectData.GetProjectData() + + If oProj.m_Err Is Nothing Then + oProj.m_Err = New ErrObject + End If + Err = oProj.m_Err + + End Function + Public Function IsArray(ByVal VarName As Object) As Boolean If VarName Is Nothing Then diff --git a/src/Microsoft.VisualBasic.Core/tests/ErrObjectTests.cs b/src/Microsoft.VisualBasic.Core/tests/ErrObjectTests.cs new file mode 100644 index 000000000000..ce134d4ce0c9 --- /dev/null +++ b/src/Microsoft.VisualBasic.Core/tests/ErrObjectTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualBasic.CompilerServices; +using System; +using Xunit; + +namespace Microsoft.VisualBasic.Tests +{ + public class ErrObjectTests + { + [Fact] + public void Clear() + { + ProjectData.SetProjectError(new ArgumentException(), 3); + var errObj = Information.Err(); + errObj.Number = 5; + errObj.Description = "Description"; + errObj.Clear(); + Assert.Equal(0, errObj.Erl); + Assert.Equal(0, errObj.LastDllError); + Assert.Equal(0, errObj.Number); + Assert.Equal("", errObj.Description); + Assert.Null(errObj.GetException()); + } + + [Fact] + public void Raise() + { + ProjectData.SetProjectError(new Exception()); + _ = Assert.Throws(() => Information.Err().Raise(0)).ToString(); + + ProjectData.SetProjectError(new Exception()); + _ = Assert.Throws(() => Information.Err().Raise(7)).ToString(); + + ProjectData.SetProjectError(new ArgumentException()); + _ = Assert.Throws(() => Information.Err().Raise(7)).ToString(); + + ProjectData.SetProjectError(new ArgumentException()); + _ = Assert.Throws(() => Information.Err().Raise(32768)).ToString(); + + ProjectData.SetProjectError(new InvalidOperationException()); + _ = Assert.Throws(() => Information.Err().Raise(1, Description: "MyDescription")).ToString(); + } + + [Fact] + public void FilterDefaultMessage() + { + ProjectData.SetProjectError(new System.IO.FileNotFoundException("Description")); + Assert.Equal("Description", Information.Err().Description); + + ProjectData.SetProjectError(new System.IO.FileNotFoundException("")); + Assert.Equal("ID53", Information.Err().Description); + + ProjectData.SetProjectError(new System.IO.FileNotFoundException("Exception from HRESULT: 0x80")); + Assert.Equal("ID53", Information.Err().Description); + } + } +} diff --git a/src/Microsoft.VisualBasic.Core/tests/Microsoft.VisualBasic.Core.Tests.csproj b/src/Microsoft.VisualBasic.Core/tests/Microsoft.VisualBasic.Core.Tests.csproj index dbb78eebb174..e5ef8c633b3e 100644 --- a/src/Microsoft.VisualBasic.Core/tests/Microsoft.VisualBasic.Core.Tests.csproj +++ b/src/Microsoft.VisualBasic.Core/tests/Microsoft.VisualBasic.Core.Tests.csproj @@ -25,11 +25,13 @@ + + diff --git a/src/Microsoft.VisualBasic.Core/tests/ProjectDataTests.cs b/src/Microsoft.VisualBasic.Core/tests/ProjectDataTests.cs new file mode 100644 index 000000000000..64887d94a052 --- /dev/null +++ b/src/Microsoft.VisualBasic.Core/tests/ProjectDataTests.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.VisualBasic.CompilerServices; +using System; +using Xunit; + +namespace Microsoft.VisualBasic.CompilerServices.Tests +{ + public class ProjectDataTests + { + [Fact] + public void CreateProjectError() + { + _ = Assert.Throws(() => ProjectData.CreateProjectError(0)).ToString(); + _ = Assert.IsType(ProjectData.CreateProjectError(1)).ToString(); + _ = Assert.IsType(ProjectData.CreateProjectError(7)).ToString(); + _ = Assert.IsType(ProjectData.CreateProjectError(32768)).ToString(); + _ = Assert.IsType(ProjectData.CreateProjectError(40068)).ToString(); + _ = Assert.IsType(ProjectData.CreateProjectError(41000)).ToString(); + } + + [Fact] + public void SetProjectError() + { + Exception e = new ArgumentException(); + ProjectData.SetProjectError(e); + Assert.Same(e, Information.Err().GetException()); + Assert.Equal(0, Information.Err().Erl); + + e = new InvalidOperationException(); + ProjectData.SetProjectError(e, 3); + Assert.Same(e, Information.Err().GetException()); + Assert.Equal(3, Information.Err().Erl); + + e = new Exception(); + ProjectData.SetProjectError(e); + Assert.Same(e, Information.Err().GetException()); + Assert.Equal(0, Information.Err().Erl); + } + + [Fact] + public void ClearProjectError() + { + ProjectData.SetProjectError(new ArgumentException(), 3); + ProjectData.ClearProjectError(); + Assert.Null(Information.Err().GetException()); + Assert.Equal(0, Information.Err().Erl); + } + } +} diff --git a/src/Microsoft.VisualBasic.Core/tests/VB/Microsoft.VisualBasic.VB.Tests.vbproj b/src/Microsoft.VisualBasic.Core/tests/VB/Microsoft.VisualBasic.VB.Core.Tests.vbproj similarity index 100% rename from src/Microsoft.VisualBasic.Core/tests/VB/Microsoft.VisualBasic.VB.Tests.vbproj rename to src/Microsoft.VisualBasic.Core/tests/VB/Microsoft.VisualBasic.VB.Core.Tests.vbproj diff --git a/src/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj b/src/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj index eaa950047080..94336afa2a42 100644 --- a/src/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj +++ b/src/Microsoft.Win32.SystemEvents/src/Microsoft.Win32.SystemEvents.csproj @@ -97,8 +97,8 @@ Common\Interop\Windows\User32\Interop.WndProc.cs - - Common\Interop\Windows\Kernel32\Interop.GetCurrentThreadId.cs + + Common\CoreLib\Interop\Windows\Kernel32\Interop.GetCurrentThreadId.cs Common\Interop\Windows\Kernel32\Interop.GetModuleHandle.cs diff --git a/src/Native/Unix/System.Native/pal_networkchange.c b/src/Native/Unix/System.Native/pal_networkchange.c index 822f7c87327d..dc1378ccfdde 100644 --- a/src/Native/Unix/System.Native/pal_networkchange.c +++ b/src/Native/Unix/System.Native/pal_networkchange.c @@ -35,7 +35,9 @@ Error SystemNative_CreateNetworkChangeListenerSocket(int32_t* retSocket) if (bind(sock, (struct sockaddr*)(&sa), sizeof(sa)) != 0) { *retSocket = -1; - return (Error)(SystemNative_ConvertErrorPlatformToPal(errno)); + Error palError = (Error)(SystemNative_ConvertErrorPlatformToPal(errno)); + close(sock); + return palError; } *retSocket = sock; diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c index 6fe52e24975c..fe04348712a4 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.c @@ -443,12 +443,50 @@ int32_t AppleCryptoNative_SslGetProtocolVersion(SSLContextRef sslContext, PAL_Ss return osStatus; } -int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint32_t* pCipherSuiteOut) +int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint16_t* pCipherSuiteOut) { if (pCipherSuiteOut == NULL) - *pCipherSuiteOut = 0; + { + return errSecParam; + } + + SSLCipherSuite cipherSuite; + OSStatus status = SSLGetNegotiatedCipher(sslContext, &cipherSuite); + *pCipherSuiteOut = (uint16_t)cipherSuite; + + return status; +} + +int32_t AppleCryptoNative_SslSetEnabledCipherSuites(SSLContextRef sslContext, const uint32_t* cipherSuites, int32_t numCipherSuites) +{ + // Max numCipherSuites is 2^16 (all possible cipher suites) + assert(numCipherSuites < (1 << 16)); + + if (sizeof(SSLCipherSuite) == sizeof(uint32_t)) + { + // macOS + return SSLSetEnabledCiphers(sslContext, cipherSuites, (size_t)numCipherSuites); + } + else + { + // iOS, tvOS, watchOS + SSLCipherSuite* cipherSuites16 = (SSLCipherSuite*)calloc((size_t)numCipherSuites, sizeof(SSLCipherSuite)); + + if (cipherSuites16 == NULL) + { + return errSSLInternal; + } - return SSLGetNegotiatedCipher(sslContext, pCipherSuiteOut); + for (int i = 0; i < numCipherSuites; i++) + { + cipherSuites16[i] = (SSLCipherSuite)cipherSuites[i]; + } + + OSStatus status = SSLSetEnabledCiphers(sslContext, cipherSuites16, (size_t)numCipherSuites); + + free(cipherSuites16); + return status; + } } __attribute__((constructor)) static void InitializeAppleCryptoSslShim() diff --git a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h index 2337b10e70df..e6c922cfffdd 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h +++ b/src/Native/Unix/System.Security.Cryptography.Native.Apple/pal_ssl.h @@ -230,7 +230,14 @@ Retrieve the TLS Cipher Suite which was negotiated for the current session. Returns the output of SSLGetNegotiatedCipher. Output: -pProtocol: The TLS CipherSuite value (from the RFC), e.g. ((uint32_t)0xC030) for +pProtocol: The TLS CipherSuite value (from the RFC), e.g. ((uint16_t)0xC030) for TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 */ -DLLEXPORT int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint32_t* pCipherSuiteOut); +DLLEXPORT int32_t AppleCryptoNative_SslGetCipherSuite(SSLContextRef sslContext, uint16_t* pCipherSuiteOut); + +/* +Sets enabled cipher suites for the current session. + +Returns the output of SSLSetEnabledCiphers. +*/ +DLLEXPORT int32_t AppleCryptoNative_SslSetEnabledCipherSuites(SSLContextRef sslContext, const uint32_t* cipherSuites, int32_t numCipherSuites); diff --git a/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt b/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt index cf897562f5a3..056001927f1b 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt +++ b/src/Native/Unix/System.Security.Cryptography.Native/CMakeLists.txt @@ -14,7 +14,11 @@ if(CMAKE_STATIC_LIB_LINK) set(CMAKE_FIND_LIBRARY_SUFFIXES .a) endif(CMAKE_STATIC_LIB_LINK) -find_package(OpenSSL REQUIRED) +find_package(OpenSSL) +if(NOT OPENSSL_FOUND) + message(FATAL_ERROR "!!! Cannot find libssl and System.Security.Cryptography.Native cannot build without it. Try installing libssl-dev (or the appropriate package for your platform) !!!") +endif(NOT OPENSSL_FOUND) + include_directories(${OPENSSL_INCLUDE_DIR}) set(NATIVECRYPTO_SOURCES diff --git a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h index 59c0d23df88f..585ff6797eb6 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/opensslshim.h @@ -36,9 +36,16 @@ #include #include "pal_crypto_config.h" +#define OPENSSL_VERSION_1_1_1_RTM 0x10101000L #define OPENSSL_VERSION_1_1_0_RTM 0x10100000L #define OPENSSL_VERSION_1_0_2_RTM 0x10002000L +#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_1_1_RTM +#define HAVE_OPENSSL_SET_CIPHERSUITES 1 +#else +#define HAVE_OPENSSL_SET_CIPHERSUITES 0 +#endif + #if OPENSSL_VERSION_NUMBER < OPENSSL_VERSION_1_1_0_RTM // Remove problematic #defines @@ -77,6 +84,14 @@ int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP* group, const EC_POINT* int EC_POINT_set_affine_coordinates_GF2m( const EC_GROUP* group, EC_POINT* p, const BIGNUM* x, const BIGNUM* y, BN_CTX* ctx); #endif + +#if !HAVE_OPENSSL_SET_CIPHERSUITES +#undef HAVE_OPENSSL_SET_CIPHERSUITES +#define HAVE_OPENSSL_SET_CIPHERSUITES 1 +int SSL_CTX_set_ciphersuites(SSL_CTX *ctx, const char *str); +const SSL_CIPHER* SSL_CIPHER_find(SSL *ssl, const unsigned char *ptr); +#endif + #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_1_0_RTM typedef struct stack_st _STACK; int CRYPTO_add_lock(int* pointer, int amount, int type, const char* file, int line); @@ -422,9 +437,11 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi REQUIRED_FUNCTION(RSA_size) \ REQUIRED_FUNCTION(RSA_up_ref) \ REQUIRED_FUNCTION(RSA_verify) \ - REQUIRED_FUNCTION(SSL_CIPHER_description) \ + LIGHTUP_FUNCTION(SSL_CIPHER_find) \ REQUIRED_FUNCTION(SSL_CIPHER_get_bits) \ REQUIRED_FUNCTION(SSL_CIPHER_get_id) \ + LIGHTUP_FUNCTION(SSL_CIPHER_get_name) \ + LIGHTUP_FUNCTION(SSL_CIPHER_get_version) \ REQUIRED_FUNCTION(SSL_ctrl) \ REQUIRED_FUNCTION(SSL_set_quiet_shutdown) \ REQUIRED_FUNCTION(SSL_CTX_check_private_key) \ @@ -436,6 +453,7 @@ void SSL_get0_alpn_selected(const SSL* ssl, const unsigned char** protocol, unsi LIGHTUP_FUNCTION(SSL_CTX_set_alpn_select_cb) \ REQUIRED_FUNCTION(SSL_CTX_set_cert_verify_callback) \ REQUIRED_FUNCTION(SSL_CTX_set_cipher_list) \ + LIGHTUP_FUNCTION(SSL_CTX_set_ciphersuites) \ REQUIRED_FUNCTION(SSL_CTX_set_client_cert_cb) \ REQUIRED_FUNCTION(SSL_CTX_set_quiet_shutdown) \ FALLBACK_FUNCTION(SSL_CTX_set_options) \ @@ -810,8 +828,10 @@ FOR_ALL_OPENSSL_FUNCTIONS #define sk_push OPENSSL_sk_push_ptr #define sk_value OPENSSL_sk_value_ptr #define SSL_CIPHER_get_bits SSL_CIPHER_get_bits_ptr -#define SSL_CIPHER_description SSL_CIPHER_description_ptr +#define SSL_CIPHER_find SSL_CIPHER_find_ptr #define SSL_CIPHER_get_id SSL_CIPHER_get_id_ptr +#define SSL_CIPHER_get_name SSL_CIPHER_get_name_ptr +#define SSL_CIPHER_get_version SSL_CIPHER_get_version_ptr #define SSL_ctrl SSL_ctrl_ptr #define SSL_set_quiet_shutdown SSL_set_quiet_shutdown_ptr #define SSL_CTX_check_private_key SSL_CTX_check_private_key_ptr @@ -822,6 +842,7 @@ FOR_ALL_OPENSSL_FUNCTIONS #define SSL_CTX_set_alpn_select_cb SSL_CTX_set_alpn_select_cb_ptr #define SSL_CTX_set_cert_verify_callback SSL_CTX_set_cert_verify_callback_ptr #define SSL_CTX_set_cipher_list SSL_CTX_set_cipher_list_ptr +#define SSL_CTX_set_ciphersuites SSL_CTX_set_ciphersuites_ptr #define SSL_CTX_set_client_cert_cb SSL_CTX_set_client_cert_cb_ptr #define SSL_CTX_set_options SSL_CTX_set_options_ptr #define SSL_CTX_set_quiet_shutdown SSL_CTX_set_quiet_shutdown_ptr diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_dsa.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_dsa.c index a9a6123fdbbd..70b40a036af3 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_dsa.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_dsa.c @@ -20,13 +20,18 @@ void CryptoNative_DsaDestroy(DSA* dsa) int32_t CryptoNative_DsaGenerateKey(DSA** dsa, int32_t bits) { - *dsa = DSA_new(); if (!dsa) { assert(false); return 0; } + *dsa = DSA_new(); + if (!(*dsa)) + { + return 0; + } + if (!DSA_generate_parameters_ex(*dsa, bits, NULL, 0, NULL, NULL, NULL) || !DSA_generate_key(*dsa)) { diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c b/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c index fec845368d9b..d23d07038b2e 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.c @@ -229,247 +229,6 @@ int32_t CryptoNative_SslSessionReused(SSL* ssl) return SSL_session_reused(ssl) == 1; } -static bool StringSpanEquals(const char* lhs, const char* rhs, size_t lhsLength) -{ - if (lhsLength != strlen(rhs)) - { - return false; - } - - return strncmp(lhs, rhs, lhsLength) == 0; -} - -static CipherAlgorithmType MapCipherAlgorithmType(const char* encryption, size_t encryptionLength) -{ - if (StringSpanEquals(encryption, "DES(56)", encryptionLength)) - return Des; - if (StringSpanEquals(encryption, "3DES(168)", encryptionLength)) - return TripleDes; - if (StringSpanEquals(encryption, "RC4(128)", encryptionLength)) - return Rc4; - if (StringSpanEquals(encryption, "RC2(128)", encryptionLength)) - return Rc2; - if (StringSpanEquals(encryption, "None", encryptionLength)) - return Null; - if (StringSpanEquals(encryption, "IDEA(128)", encryptionLength)) - return SSL_IDEA; - if (StringSpanEquals(encryption, "SEED(128)", encryptionLength)) - return SSL_SEED; - if (StringSpanEquals(encryption, "AES(128)", encryptionLength)) - return Aes128; - if (StringSpanEquals(encryption, "AES(256)", encryptionLength)) - return Aes256; - if (StringSpanEquals(encryption, "Camellia(128)", encryptionLength)) - return SSL_CAMELLIA128; - if (StringSpanEquals(encryption, "Camellia(256)", encryptionLength)) - return SSL_CAMELLIA256; - if (StringSpanEquals(encryption, "GOST89(256)", encryptionLength)) - return SSL_eGOST2814789CNT; - if (StringSpanEquals(encryption, "AESGCM(128)", encryptionLength)) - return Aes128; - if (StringSpanEquals(encryption, "AESGCM(256)", encryptionLength)) - return Aes256; - - return CipherAlgorithmType_None; -} - -static ExchangeAlgorithmType MapExchangeAlgorithmType(const char* keyExchange, size_t keyExchangeLength) -{ - if (StringSpanEquals(keyExchange, "RSA", keyExchangeLength)) - return RsaKeyX; - if (StringSpanEquals(keyExchange, "DH/RSA", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "DH/DSS", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "DH", keyExchangeLength)) - return DiffieHellman; - if (StringSpanEquals(keyExchange, "KRB5", keyExchangeLength)) - return SSL_kKRB5; - if (StringSpanEquals(keyExchange, "ECDH", keyExchangeLength)) - return SSL_ECDHE; - if (StringSpanEquals(keyExchange, "ECDH/RSA", keyExchangeLength)) - return SSL_ECDH; - if (StringSpanEquals(keyExchange, "ECDH/ECDSA", keyExchangeLength)) - return SSL_ECDSA; - if (StringSpanEquals(keyExchange, "PSK", keyExchangeLength)) - return SSL_kPSK; - if (StringSpanEquals(keyExchange, "GOST", keyExchangeLength)) - return SSL_kGOST; - if (StringSpanEquals(keyExchange, "SRP", keyExchangeLength)) - return SSL_kSRP; - - return ExchangeAlgorithmType_None; -} - -static void GetHashAlgorithmTypeAndSize(const char* mac, - size_t macLength, - HashAlgorithmType* dataHashAlg, - DataHashSize* hashKeySize) -{ - if (StringSpanEquals(mac, "MD5", macLength)) - { - *dataHashAlg = Md5; - *hashKeySize = MD5_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA1", macLength)) - { - *dataHashAlg = Sha1; - *hashKeySize = SHA1_HashKeySize; - return; - } - if (StringSpanEquals(mac, "GOST94", macLength)) - { - *dataHashAlg = SSL_GOST94; - *hashKeySize = GOST_HashKeySize; - return; - } - if (StringSpanEquals(mac, "GOST89", macLength)) - { - *dataHashAlg = SSL_GOST89; - *hashKeySize = GOST_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA256", macLength)) - { - *dataHashAlg = SSL_SHA256; - *hashKeySize = SHA256_HashKeySize; - return; - } - if (StringSpanEquals(mac, "SHA384", macLength)) - { - *dataHashAlg = SSL_SHA384; - *hashKeySize = SHA384_HashKeySize; - return; - } - if (StringSpanEquals(mac, "AEAD", macLength)) - { - *dataHashAlg = SSL_AEAD; - *hashKeySize = Default; - return; - } - - *dataHashAlg = HashAlgorithmType_None; - *hashKeySize = Default; -} - -/* -Given a keyName string like "Enc=XXX", parses the description string and returns the -'XXX' into value and valueLength return variables. - -Returns a value indicating whether the pattern starting with keyName was found in description. -*/ -static bool GetDescriptionValue( - const char* description, const char* keyName, size_t keyNameLength, const char** value, size_t* valueLength) -{ - // search for keyName in description - const char* keyNameStart = strstr(description, keyName); - if (keyNameStart != NULL) - { - // set valueStart to the beginning of the value - const char* valueStart = keyNameStart + keyNameLength; - size_t index = 0; - - // the value ends when we hit a space or the end of the string - while (valueStart[index] != ' ' && valueStart[index] != '\0') - { - index++; - } - - *value = valueStart; - *valueLength = index; - return true; - } - - return false; -} - -#define descriptionLength 256 - -/* -Parses the Kx, Enc, and Mac values out of the SSL_CIPHER_description and -maps the values to the corresponding .NET enum value. -*/ -static bool GetSslConnectionInfoFromDescription(const SSL_CIPHER* cipher, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - DataHashSize* hashKeySize) -{ - char description[descriptionLength] = { 0 }; - SSL_CIPHER_description(cipher, description, descriptionLength - 1); // ensure description is NULL-terminated - - const char* keyExchange; - size_t keyExchangeLength; - if (!GetDescriptionValue(description, "Kx=", 3, &keyExchange, &keyExchangeLength)) - { - return false; - } - - const char* encryption; - size_t encryptionLength; - if (!GetDescriptionValue(description, "Enc=", 4, &encryption, &encryptionLength)) - { - return false; - } - - const char* mac; - size_t macLength; - if (!GetDescriptionValue(description, "Mac=", 4, &mac, &macLength)) - { - return false; - } - - *keyExchangeAlg = MapExchangeAlgorithmType(keyExchange, keyExchangeLength); - *dataCipherAlg = MapCipherAlgorithmType(encryption, encryptionLength); - GetHashAlgorithmTypeAndSize(mac, macLength, dataHashAlg, hashKeySize); - return true; -} - -int32_t CryptoNative_GetSslConnectionInfo(SSL* ssl, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - int32_t* dataKeySize, - DataHashSize* hashKeySize) -{ - const SSL_CIPHER* cipher; - - if (!ssl || !dataCipherAlg || !keyExchangeAlg || !dataHashAlg || !dataKeySize || !hashKeySize) - { - goto err; - } - - cipher = SSL_get_current_cipher(ssl); - if (!cipher) - { - goto err; - } - - SSL_CIPHER_get_bits(cipher, dataKeySize); - - if (GetSslConnectionInfoFromDescription(cipher, dataCipherAlg, keyExchangeAlg, dataHashAlg, hashKeySize)) - { - return 1; - } - -err: - assert(false); - - if (dataCipherAlg) - *dataCipherAlg = CipherAlgorithmType_None; - if (keyExchangeAlg) - *keyExchangeAlg = ExchangeAlgorithmType_None; - if (dataHashAlg) - *dataHashAlg = HashAlgorithmType_None; - if (dataKeySize) - *dataKeySize = 0; - if (hashKeySize) - *hashKeySize = Default; - - return 0; -} - int32_t CryptoNative_SslWrite(SSL* ssl, const void* buf, int32_t num) { return SSL_write(ssl, buf, num); @@ -560,46 +319,117 @@ CryptoNative_SslCtxSetCertVerifyCallback(SSL_CTX* ctx, SslCtxSetCertVerifyCallba SSL_CTX_set_cert_verify_callback(ctx, callback, arg); } -// delimiter ":" is used to allow more than one strings -// below string is corresponding to "AllowNoEncryption" -#define SSL_TXT_Separator ":" -#define SSL_TXT_Exclusion "!" -#define SSL_TXT_AllIncludingNull SSL_TXT_ALL SSL_TXT_Separator SSL_TXT_eNULL -#define SSL_TXT_NotAnon SSL_TXT_Separator SSL_TXT_Exclusion SSL_TXT_aNULL - int32_t CryptoNative_SetEncryptionPolicy(SSL_CTX* ctx, EncryptionPolicy policy) { - const char* cipherString = NULL; - bool clearSecLevel = false; - switch (policy) { + case AllowNoEncryption: + case NoEncryption: + // No minimum security policy, same as OpenSSL 1.0 + SSL_CTX_set_security_level(ctx, 0); + return true; case RequireEncryption: - cipherString = SSL_TXT_ALL SSL_TXT_NotAnon; - break; + return true; + } - case AllowNoEncryption: - cipherString = SSL_TXT_AllIncludingNull; - clearSecLevel = true; - break; + return false; +} - case NoEncryption: - cipherString = SSL_TXT_eNULL; - clearSecLevel = true; - break; +int32_t CryptoNative_SetCiphers(SSL_CTX* ctx, const char* cipherList, const char* cipherSuites) +{ + int32_t ret = true; + + // for < TLS 1.3 + if (cipherList != NULL) + { + ret &= SSL_CTX_set_cipher_list(ctx, cipherList); + if (!ret) + { + return ret; + } } - assert(cipherString != NULL); + // for TLS 1.3 +#if HAVE_OPENSSL_SET_CIPHERSUITES + if (CryptoNative_Tls13Supported() && cipherSuites != NULL) + { + ret &= SSL_CTX_set_ciphersuites(ctx, cipherSuites); + } +#else + (void)cipherSuites; +#endif + + return ret; +} + +const char* CryptoNative_GetOpenSslCipherSuiteName(SSL* ssl, int32_t cipherSuite, int32_t* isTls12OrLower) +{ +#if HAVE_OPENSSL_SET_CIPHERSUITES + unsigned char cs[2]; + const SSL_CIPHER* cipher; + const char* ret; + + *isTls12OrLower = 0; + cs[0] = (cipherSuite >> 8) & 0xFF; + cs[1] = cipherSuite & 0xFF; + cipher = SSL_CIPHER_find(ssl, cs); + + if (cipher == NULL) + return NULL; + + ret = SSL_CIPHER_get_name(cipher); + + if (ret == NULL) + return NULL; + + // we should get (NONE) only when cipher is NULL + assert(strcmp("(NONE)", ret) != 0); + + const char* version = SSL_CIPHER_get_version(cipher); + assert(version != NULL); + assert(strcmp(version, "unknown") != 0); - if (clearSecLevel) + // same rules apply for DTLS as for TLS so just shortcut + if (version[0] == 'D') { - // No minimum security policy, same as OpenSSL 1.0 - SSL_CTX_set_security_level(ctx, 0); + version++; } - return SSL_CTX_set_cipher_list(ctx, cipherString); + // check if tls1.2 or lower + // check most common case first + if (strncmp("TLSv1", version, 5) == 0) + { + const char* tlsver = version + 5; + // true for TLSv1, TLSv1.0, TLSv1.1, TLS1.2, anything else is assumed to be newer + *isTls12OrLower = + tlsver[0] == 0 || + (tlsver[0] == '.' && tlsver[1] >= '0' && tlsver[1] <= '2' && tlsver[2] == 0); + } + else + { + // if we don't know it assume it is new + // worst case scenario OpenSSL will ignore it + *isTls12OrLower = + strncmp("SSLv", version, 4) == 0; + } + + return ret; +#else + (void)ssl; + (void)cipherSuite; + *isTls12OrLower = 0; + return NULL; +#endif } +int32_t CryptoNative_Tls13Supported() +{ +#if HAVE_OPENSSL_SET_CIPHERSUITES + return API_EXISTS(SSL_CTX_set_ciphersuites); +#else + return false; +#endif +} void CryptoNative_SslCtxSetClientCertCallback(SSL_CTX* ctx, SslClientCertCallback callback) { diff --git a/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h b/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h index 038e5de7cd36..2db6e76d01cd 100644 --- a/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h +++ b/src/Native/Unix/System.Security.Cryptography.Native/pal_ssl.h @@ -204,19 +204,6 @@ Returns the protocol version string for the SSL instance. */ DLLEXPORT const char* CryptoNative_SslGetVersion(SSL* ssl); -/* -Returns the connection information for the SSL instance. - -Returns 1 upon success, otherwise 0. -*/ - -DLLEXPORT int32_t CryptoNative_GetSslConnectionInfo(SSL* ssl, - CipherAlgorithmType* dataCipherAlg, - ExchangeAlgorithmType* keyExchangeAlg, - HashAlgorithmType* dataHashAlg, - int32_t* dataKeySize, - DataHashSize* hashKeySize); - /* Shims the SSL_write method. @@ -338,10 +325,19 @@ CryptoNative_SslCtxSetCertVerifyCallback(SSL_CTX* ctx, SslCtxSetCertVerifyCallba /* Sets the specified encryption policy on the SSL_CTX. -Returns 1 if any cipher could be selected, and 0 if none were available. */ DLLEXPORT int32_t CryptoNative_SetEncryptionPolicy(SSL_CTX* ctx, EncryptionPolicy policy); +/* +Sets ciphers (< TLS 1.3) and cipher suites (TLS 1.3) on the SSL_CTX +*/ +DLLEXPORT int32_t CryptoNative_SetCiphers(SSL_CTX* ctx, const char* cipherList, const char* cipherSuites); + +/* +Determines if TLS 1.3 is supported by this OpenSSL implementation +*/ +DLLEXPORT int32_t CryptoNative_Tls13Supported(void); + /* Shims the SSL_CTX_set_client_cert_cb method */ @@ -396,3 +392,9 @@ DLLEXPORT int32_t CryptoNative_SslSetTlsExtHostName(SSL* ssl, uint8_t* name); Shims the SSL_get_current_cipher and SSL_CIPHER_get_id. */ DLLEXPORT int32_t CryptoNative_SslGetCurrentCipherId(SSL* ssl, int32_t* cipherId); + +/* +Looks up a cipher by the IANA identifier, returns a shared string for the OpenSSL name for the cipher, +and emits a value indicating if the cipher belongs to the SSL2-TLS1.2 list, or the TLS1.3+ list. +*/ +DLLEXPORT const char* CryptoNative_GetOpenSslCipherSuiteName(SSL* ssl, int32_t cipherSuite, int32_t* isTls12OrLower); diff --git a/src/Native/Windows/clrcompression/zlib-intel/deflate_medium.c b/src/Native/Windows/clrcompression/zlib-intel/deflate_medium.c index f6f671cd6ca3..582e8b2e5ac4 100644 --- a/src/Native/Windows/clrcompression/zlib-intel/deflate_medium.c +++ b/src/Native/Windows/clrcompression/zlib-intel/deflate_medium.c @@ -94,8 +94,10 @@ static void insert_match(deflate_state *s, struct match match) match.strstart += match.match_length; match.match_length = 0; s->ins_h = s->window[match.strstart]; - if (match.strstart >= 1) - UPDATE_HASH(s, s->ins_h, match.strstart+2-MIN_MATCH); + if (match.strstart >= 1) { + IPos hash_head = 0; + INSERT_STRING(s, match.strstart - 1, hash_head); + } #if MIN_MATCH != 3 #warning Call UPDATE_HASH() MIN_MATCH-3 more times #endif @@ -214,6 +216,9 @@ block_state deflate_medium(deflate_state *s, int flush) if (s->lookahead >= MIN_MATCH) { INSERT_STRING(s, s->strstart, hash_head); } + + if (hash_head && hash_head == s->strstart) + hash_head--; /* set up the initial match to be a 1 byte literal */ current_match.match_start = 0; @@ -247,6 +252,9 @@ block_state deflate_medium(deflate_state *s, int flush) if (s->lookahead > MIN_LOOKAHEAD) { s->strstart = current_match.strstart + current_match.match_length; INSERT_STRING(s, s->strstart, hash_head); + + if (hash_head && hash_head == s->strstart) + hash_head--; /* set up the initial match to be a 1 byte literal */ next_match.match_start = 0; diff --git a/src/Native/Windows/clrcompression/zlib-intel/inflate.c b/src/Native/Windows/clrcompression/zlib-intel/inflate.c index 6179e166562a..165e465a0847 100644 --- a/src/Native/Windows/clrcompression/zlib-intel/inflate.c +++ b/src/Native/Windows/clrcompression/zlib-intel/inflate.c @@ -239,6 +239,9 @@ int stream_size; return ret; } + if (state->wbits == 0) + state->wbits = 15; + if (state->wbits > 0) { state->wsize = 1UL << state->wbits; state->window = (unsigned char FAR *)ZALLOC(strm, state->wsize + 16, 4); diff --git a/src/Native/build-native.proj b/src/Native/build-native.proj index 0edf486adf52..507ec1861aee 100644 --- a/src/Native/build-native.proj +++ b/src/Native/build-native.proj @@ -24,12 +24,12 @@ <_PortableBuildArg Condition="'$(PortableBuild)' == 'true'"> -portable - <_BuildNativeClangArg Condition="'$(BuildNativeClang)' != ''"> $(BuildNativeClang) + <_BuildNativeCompilerArg Condition="'$(BuildNativeCompiler)' != ''"> $(BuildNativeCompiler) - <_BuildNativeUnixArgs>$(_BuildNativeArgs)$(_ProcessCountArg)$(_StripSymbolsArg)$(_PortableBuildArg)$(_BuildNativeClangArg) + <_BuildNativeUnixArgs>$(_BuildNativeArgs)$(_ProcessCountArg)$(_StripSymbolsArg)$(_PortableBuildArg)$(_BuildNativeCompilerArg) diff --git a/src/System.AppContext/src/Configurations.props b/src/System.AppContext/src/Configurations.props index 5d1182962c9e..81407aeeeac8 100644 --- a/src/System.AppContext/src/Configurations.props +++ b/src/System.AppContext/src/Configurations.props @@ -2,8 +2,6 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.AppContext/src/System.AppContext.csproj b/src/System.AppContext/src/System.AppContext.csproj index c4e5e4314108..cbc30b2b19ea 100644 --- a/src/System.AppContext/src/System.AppContext.csproj +++ b/src/System.AppContext/src/System.AppContext.csproj @@ -4,7 +4,7 @@ {5522BAFC-E2FF-4896-993A-401DDEDFD85F} true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Buffers/src/Configurations.props b/src/System.Buffers/src/Configurations.props index 5d1182962c9e..81407aeeeac8 100644 --- a/src/System.Buffers/src/Configurations.props +++ b/src/System.Buffers/src/Configurations.props @@ -2,8 +2,6 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Buffers/src/System.Buffers.csproj b/src/System.Buffers/src/System.Buffers.csproj index 833c7d8a8fd5..e08ed5c77de6 100644 --- a/src/System.Buffers/src/System.Buffers.csproj +++ b/src/System.Buffers/src/System.Buffers.csproj @@ -4,7 +4,7 @@ true true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Collections.Concurrent/src/Configurations.props b/src/System.Collections.Concurrent/src/Configurations.props index 4002bb571203..dc04b5e626d2 100644 --- a/src/System.Collections.Concurrent/src/Configurations.props +++ b/src/System.Collections.Concurrent/src/Configurations.props @@ -3,9 +3,8 @@ netcoreapp-Unix; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; + uapaot-Windows_NT; diff --git a/src/System.Collections.Concurrent/src/System.Collections.Concurrent.csproj b/src/System.Collections.Concurrent/src/System.Collections.Concurrent.csproj index d56d5a9af995..5abdb75d19c2 100644 --- a/src/System.Collections.Concurrent/src/System.Collections.Concurrent.csproj +++ b/src/System.Collections.Concurrent/src/System.Collections.Concurrent.csproj @@ -4,7 +4,7 @@ System.Collections.Concurrent System.Collections.Concurrent true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Collections/src/Configurations.props b/src/System.Collections/src/Configurations.props index 78e091b28e29..0adb3f76b008 100644 --- a/src/System.Collections/src/Configurations.props +++ b/src/System.Collections/src/Configurations.props @@ -3,8 +3,6 @@ uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; diff --git a/src/System.Collections/src/System.Collections.csproj b/src/System.Collections/src/System.Collections.csproj index c1da123cbc61..c85db3f39ca3 100644 --- a/src/System.Collections/src/System.Collections.csproj +++ b/src/System.Collections/src/System.Collections.csproj @@ -4,7 +4,7 @@ System.Collections true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ArrayConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ArrayConverter.cs index b16df4f8b697..b900518b3290 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ArrayConverter.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ArrayConverter.cs @@ -17,11 +17,6 @@ public class ArrayConverter : CollectionConverter /// public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - if (destinationType == null) - { - throw new ArgumentNullException(nameof(destinationType)); - } - if (destinationType == typeof(string) && value is Array) { return SR.Format(SR.Array, value.GetType().Name); @@ -39,22 +34,20 @@ public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContex { return null; } - - PropertyDescriptor[] props = null; - - if (value.GetType().IsArray) + if (!(value is Array valueArray)) { - Array valueArray = (Array)value; - int length = valueArray.GetLength(0); - props = new PropertyDescriptor[length]; + return new PropertyDescriptorCollection(null); + } - Type arrayType = value.GetType(); - Type elementType = arrayType.GetElementType(); + int length = valueArray.GetLength(0); + PropertyDescriptor[] props = new PropertyDescriptor[length]; - for (int i = 0; i < length; i++) - { - props[i] = new ArrayPropertyDescriptor(arrayType, elementType, i); - } + Type arrayType = value.GetType(); + Type elementType = arrayType.GetElementType(); + + for (int i = 0; i < length; i++) + { + props[i] = new ArrayPropertyDescriptor(arrayType, elementType, i); } return new PropertyDescriptorCollection(props); diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/BaseNumberConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/BaseNumberConverter.cs index d6c00f34cb48..b631cde30d84 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/BaseNumberConverter.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/BaseNumberConverter.cs @@ -19,10 +19,7 @@ public abstract class BaseNumberConverter : TypeConverter /// /// The Type this converter is targeting (e.g. Int16, UInt32, etc.) /// - internal abstract Type TargetType - { - get; - } + internal abstract Type TargetType { get; } /// /// Convert the given value to a string using the given radix @@ -33,7 +30,7 @@ internal abstract Type TargetType /// Convert the given value to a string using the given formatInfo /// internal abstract object FromString(string value, NumberFormatInfo formatInfo); - + /// /// Convert the given value from a string using the given formatInfo /// @@ -53,8 +50,7 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT /// public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { - string text = value as string; - if (text != null) + if (value is string text) { text = text.Trim(); @@ -64,8 +60,8 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { return FromString(text.Substring(1), 16); } - else if (AllowHex && text.StartsWith("0x", StringComparison.OrdinalIgnoreCase) - || text.StartsWith("&h", StringComparison.OrdinalIgnoreCase)) + else if (AllowHex && (text.StartsWith("0x", StringComparison.OrdinalIgnoreCase) + || text.StartsWith("&h", StringComparison.OrdinalIgnoreCase))) { return FromString(text.Substring(2), 16); } @@ -75,6 +71,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { culture = CultureInfo.CurrentCulture; } + NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)); return FromString(text, formatInfo); } @@ -84,6 +81,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c throw new ArgumentException(SR.Format(SR.ConvertInvalidPrimitive, text, TargetType.Name), nameof(value), e); } } + return base.ConvertFrom(context, culture, value); } @@ -103,6 +101,7 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { culture = CultureInfo.CurrentCulture; } + NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)); return ToString(value, formatInfo); } @@ -111,13 +110,18 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { return Convert.ChangeType(value, destinationType, culture); } + return base.ConvertTo(context, culture, value, destinationType); } public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { - return base.CanConvertTo(context, destinationType) || destinationType.IsPrimitive; + if (destinationType != null && destinationType.IsPrimitive) + { + return true; + } + + return base.CanConvertTo(context, destinationType); } } } - diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CharConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CharConverter.cs index 6343a075467c..d610ff524704 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CharConverter.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CharConverter.cs @@ -7,8 +7,8 @@ namespace System.ComponentModel { /// - /// Provides a type converter to convert Unicode character objects - /// to and from various other representations. + /// Provides a type converter to convert Unicode character objects to and from various + /// other representations. /// public class CharConverter : TypeConverter { @@ -26,11 +26,11 @@ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceT /// public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - if (destinationType == typeof(string) && value is char) + if (destinationType == typeof(string) && value is char charValue) { - if ((char)value == (char)0) + if (charValue == '\0') { - return ""; + return string.Empty; } } @@ -55,6 +55,7 @@ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo c { throw new FormatException(SR.Format(SR.ConvertInvalidPrimitive, text, nameof(Char))); } + return text[0]; } diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CollectionConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CollectionConverter.cs index 52a16ec40b10..5fe378eeba98 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CollectionConverter.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/CollectionConverter.cs @@ -8,7 +8,8 @@ namespace System.ComponentModel { /// - /// Provides a type converter to convert collection objects to and from various other representations. + /// Provides a type converter to convert collection objects to and from various other + /// representations. /// public class CollectionConverter : TypeConverter { @@ -17,11 +18,6 @@ public class CollectionConverter : TypeConverter /// public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - if (destinationType == null) - { - throw new ArgumentNullException(nameof(destinationType)); - } - if (destinationType == typeof(string) && value is ICollection) { return SR.Collection; @@ -31,17 +27,12 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul } /// - /// Gets a collection of properties for the type of array specified by the value parameter using - /// the specified context and attributes. + /// Gets a collection of properties for the type of array specified by the value + /// parameter using the specified context and attributes. /// public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { return new PropertyDescriptorCollection(null); } - - /// - /// Gets a value indicating whether this object supports properties. - /// - public override bool GetPropertiesSupported(ITypeDescriptorContext context) => false; } } diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/DecimalConverter.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/DecimalConverter.cs index 6a79aa73ea47..36c64ca577b0 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/DecimalConverter.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/DecimalConverter.cs @@ -43,28 +43,13 @@ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinati /// public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { - if (destinationType == null) + if (destinationType == typeof(InstanceDescriptor) && value is decimal decimalValue) { - throw new ArgumentNullException(nameof(destinationType)); + ConstructorInfo ctor = typeof(decimal).GetConstructor(new Type[] { typeof(int[]) }); + Debug.Assert(ctor != null, "Expected constructor to exist."); + return new InstanceDescriptor(ctor, new object[] { decimal.GetBits(decimalValue) }); } - if (destinationType == typeof(InstanceDescriptor) && value is decimal) - { - - object[] args = new object[] { decimal.GetBits((decimal)value) }; - MemberInfo member = typeof(decimal).GetConstructor(new Type[] { typeof(int[]) }); - - Debug.Assert(member != null, "Could not convert decimal to member. Did someone change method name / signature and not update DecimalConverter?"); - if (member != null) - { - return new InstanceDescriptor(member, args); - } - else - { - return null; - } - } - return base.ConvertTo(context, culture, value, destinationType); } @@ -93,4 +78,3 @@ internal override string ToString(object value, NumberFormatInfo formatInfo) } } } - diff --git a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectPropertyDescriptor.cs b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectPropertyDescriptor.cs index 1db5725ff542..154d8a4ea648 100644 --- a/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectPropertyDescriptor.cs +++ b/src/System.ComponentModel.TypeConverter/src/System/ComponentModel/ReflectPropertyDescriptor.cs @@ -70,9 +70,7 @@ internal sealed class ReflectPropertyDescriptor : PropertyDescriptor private MethodInfo _shouldSerializeMethod; // the should serialize method private MethodInfo _resetMethod; // the reset property method private EventDescriptor _realChangedEvent; // Changed event handler on object -#if FEATURE_INOTIFYPROPERTYCHANGED private EventDescriptor _realIPropChangedEvent; // INotifyPropertyChanged.PropertyChanged event handler on object -#endif private readonly Type _receiverType; // Only set if we are an extender /// @@ -239,7 +237,6 @@ private EventDescriptor IPropChangedEventValue { get { -#if FEATURE_INOTIFYPROPERTYCHANGED if (!_state[s_bitIPropChangedQueried]) { if (typeof(INotifyPropertyChanged).IsAssignableFrom(ComponentType)) @@ -251,9 +248,6 @@ private EventDescriptor IPropChangedEventValue } return _realIPropChangedEvent; -#else - return null; -#endif } } diff --git a/src/System.ComponentModel.TypeConverter/tests/ArrayConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/ArrayConverterTests.cs index a1cc384776a0..48dcea8c0e76 100644 --- a/src/System.ComponentModel.TypeConverter/tests/ArrayConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/ArrayConverterTests.cs @@ -2,27 +2,145 @@ // 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.Globalization; -using Microsoft.DotNet.RemoteExecutor; +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using Xunit; namespace System.ComponentModel.Tests { - public class ArrayConverterTests : ConverterTestBase + public class ArrayConverterTests : TypeConverterTestBase { + public override TypeConverter Converter => new ArrayConverter(); + + public override bool PropertiesSupported => true; + + public override IEnumerable ConvertToTestData() + { + yield return ConvertTest.Valid(new int[] { 1, 2 }, "Int32[] Array").WithInvariantRemoteInvokeCulture(); + yield return ConvertTest.Valid(1, "1"); + + yield return ConvertTest.CantConvertTo(new int[] { 1, 2 }, typeof(int[])); + yield return ConvertTest.CantConvertTo(new int[] { 1, 2 }, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo(new int[] { 1, 2 }, typeof(object)); + } + [Fact] - public static void ConvertTo_WithContext() + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Throws NullReferenceException in .NET Framework.")] + public void GetProperties_NullValue_ReturnsNull() { - RemoteExecutor.Invoke(() => + Assert.Null(Converter.GetProperties(null)); + } + + [Fact] + public void GetProperties_NonArrayValue_ReturnsEmpty() + { + Assert.Empty(Converter.GetProperties("stringValue")); + } + + [Fact] + public void GetProperties_ArrayValue_PropertiesReturnsExpected() + { + var array = new int[] { 1, 2, 3 }; + PropertyDescriptorCollection properties = Converter.GetProperties(array); + Assert.Equal(array.Length, properties.Count); + + for (int i = 0; i < array.Length; i++) + { + PropertyDescriptor property = properties[i]; + Assert.Equal(typeof(int[]), property.ComponentType); + Assert.Equal(typeof(int), property.PropertyType); + Assert.Equal($"[{i}]", property.Name); + Assert.Empty(property.Attributes); + } + } + + [Fact] + public void GetProperties_SetValue_GetValueReturnsExpected() + { + var array = new int[] { 1, 2, 3 }; + PropertyDescriptorCollection properties = Converter.GetProperties(array); + for (int i = 0; i < array.Length; i++) { - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; - - ConvertTo_WithContext(new object[1, 3] - { - { new int[2] { 1, 2 }, "Int32[] Array", null } - }, - new ArrayConverter()); - }).Dispose(); + PropertyDescriptor property = properties[i]; + Assert.Equal(array[i], property.GetValue(array)); + property.SetValue(array, -1); + Assert.Equal(-1, array[i]); + } + } + + [Fact] + public void GetProperties_SetValueWithHandler_CallsValueChanged() + { + var array = new int[] { 1, 2, 3 }; + PropertyDescriptor property = Converter.GetProperties(array)[0]; + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(array, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + + // With handler. + property.AddValueChanged(array, handler); + property.SetValue(array, 0); + Assert.Equal(new int[] { 0, 2, 3 }, array); + Assert.Equal(1, callCount); + + // Remove handler. + property.RemoveValueChanged(array, handler); + property.SetValue(array, 1); + Assert.Equal(new int[] { 1, 2, 3 }, array); + Assert.Equal(1, callCount); + } + + [Fact] + public void GetProperties_SetValueOutOfRangeWithHandler_CallsValueChanged() + { + var array = new int[] { 1, 2, 3 }; + var smallerArray = new int[] { 1, 2 }; + PropertyDescriptor property = Converter.GetProperties(array)[2]; + int callCount = 0; + EventHandler handler = (sender, e) => + { + Assert.Same(smallerArray, sender); + Assert.Same(EventArgs.Empty, e); + callCount++; + }; + + // With handler. + property.AddValueChanged(smallerArray, handler); + property.SetValue(smallerArray, 0); + Assert.Equal(new int[] { 1, 2 }, smallerArray); + Assert.Equal(new int[] { 1, 2, 3 }, array); + Assert.Equal(1, callCount); + + // Remove handler. + property.RemoveValueChanged(smallerArray, handler); + property.SetValue(smallerArray, 1); + Assert.Equal(new int[] { 1, 2 }, smallerArray); + Assert.Equal(new int[] { 1, 2, 3 }, array); + Assert.Equal(1, callCount); + } + + [Theory] + [InlineData(null)] + [InlineData("nonArrayValue")] + public void GetProperties_GetSetInvalidValue_Nop(string value) + { + var array = new int[] { 1, 2, 3 }; + PropertyDescriptor property = Converter.GetProperties(array)[2]; + Assert.Null(property.GetValue(value)); + property.SetValue(null, -1); + } + + [Fact] + public void GetProperties_GetSetDifferentIndex_Nop() + { + var array = new object[] { 1, 2, "3" }; + PropertyDescriptor finalProperty = Converter.GetProperties(array)[2]; + Assert.Null(finalProperty.GetValue(new int[2])); + finalProperty.SetValue(new int[2], -1); } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/BaseNumberConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/BaseNumberConverterTests.cs new file mode 100644 index 000000000000..ccd780c53d2d --- /dev/null +++ b/src/System.ComponentModel.TypeConverter/tests/BaseNumberConverterTests.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; +using System.Globalization; +using Xunit; + +namespace System.ComponentModel.Tests +{ + public abstract class BaseNumberConverterTests : TypeConverterTestBase + { + public override IEnumerable ConvertFromTestData() + { + yield return ConvertTest.Throws(""); + yield return ConvertTest.Throws("bad"); + yield return ConvertTest.Throws("0x"); + yield return ConvertTest.Throws("0X"); + yield return ConvertTest.Throws("1x1"); + yield return ConvertTest.Throws("0y1"); + yield return ConvertTest.Throws("&h"); + yield return ConvertTest.Throws("&H"); + yield return ConvertTest.Throws("0h1"); + yield return ConvertTest.Throws("&i1"); + + yield return ConvertTest.CantConvertFrom(new object()); + } + + public class CustomPositiveSymbolCulture : CultureInfo + { + public CustomPositiveSymbolCulture() : base("en-GB") + { + } + + public override object GetFormat(Type formatType) + { + Assert.Equal(typeof(NumberFormatInfo), formatType); + + return new NumberFormatInfo + { + PositiveSign = "!", + NegativeSign = "?" + }; + } + } + } +} diff --git a/src/System.ComponentModel.TypeConverter/tests/BooleanConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/BooleanConverterTests.cs index 75f0dca9ffb4..8cba7784ebae 100644 --- a/src/System.ComponentModel.TypeConverter/tests/BooleanConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/BooleanConverterTests.cs @@ -2,42 +2,49 @@ // 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.Collections; +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; +using System.Linq; using Xunit; namespace System.ComponentModel.Tests { - public class BooleanConverterTests : ConverterTestBase + public class BooleanConverterTests : TypeConverterTestBase { - private static BooleanConverter s_converter = new BooleanConverter(); + public override TypeConverter Converter => new BooleanConverter(); - [Fact] - public static void CanConvertFrom_WithContext() + public override bool StandardValuesSupported => true; + public override bool StandardValuesExclusive => true; + + public override IEnumerable ConvertToTestData() { - CanConvertFrom_WithContext(new object[2, 2] - { - { typeof(string), true }, - { typeof(int), false } - }, - BooleanConverterTests.s_converter); + yield return ConvertTest.Valid(true, Boolean.TrueString); + yield return ConvertTest.Valid(1, "1"); + + yield return ConvertTest.CantConvertTo(true, typeof(bool)); + yield return ConvertTest.CantConvertTo(true, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo(true, typeof(object)); } - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertFrom_WithContext(new object[2, 3] - { - { "false ", false, null }, - { "true", true, CultureInfo.InvariantCulture } - }, - BooleanConverterTests.s_converter); + yield return ConvertTest.Valid("false ", false); + yield return ConvertTest.Valid("true", true, CultureInfo.InvariantCulture); + + yield return ConvertTest.Throws("1"); + + yield return ConvertTest.CantConvertFrom(1); + yield return ConvertTest.CantConvertFrom(null); } [Fact] - public static void ConvertFrom_WithContext_Negative() + public void StandardValues_Get_ReturnsExpected() { - Assert.Throws( - () => BooleanConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "1")); + ICollection values = Converter.GetStandardValues(); + Assert.Same(values, Converter.GetStandardValues()); + Assert.Equal(new object[] { true, false }, values.Cast()); } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/ByteConvertersTests.cs b/src/System.ComponentModel.TypeConverter/tests/ByteConvertersTests.cs index d36a5962550b..98167d302825 100644 --- a/src/System.ComponentModel.TypeConverter/tests/ByteConvertersTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/ByteConvertersTests.cs @@ -2,71 +2,49 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class ByteConverterTests : ConverterTestBase + public class ByteConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new ByteConverter(); + public override TypeConverter Converter => new ByteConverter(); - [Fact] - public static void CanConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - CanConvertFrom_WithContext(new object[2, 2] - { - { typeof(string), true }, - { typeof(int), false } - }, - ByteConverterTests.s_converter); - } + yield return ConvertTest.Valid((byte)1, "1"); + yield return ConvertTest.Valid((byte)2, (byte)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((byte)3, (float)3.0); - [Fact] - public static void CanConvertTo_WithContext() - { - CanConvertTo_WithContext(new object[3, 2] - { - { typeof(string), true }, - { typeof(byte), true }, - { typeof(float), true } - }, - ByteConverterTests.s_converter); + yield return ConvertTest.CantConvertTo((byte)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((byte)3, typeof(object)); } - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertFrom_WithContext(new object[7, 3] - { - { "1 ", (byte)1, null }, - { "#2", (byte)2, null }, - { "0x3", (byte)3, null }, - { "0X4", (byte)4, null }, - { "&h5", (byte)5, null }, - { "&H6", (byte)6, null }, - { "+7", (byte)7, CultureInfo.InvariantCulture } - }, - ByteConverterTests.s_converter); - } + yield return ConvertTest.Valid("1", (byte)1); + yield return ConvertTest.Valid("#2", (byte)2); + yield return ConvertTest.Valid(" #2 ", (byte)2); + yield return ConvertTest.Valid("0x3", (byte)3); + yield return ConvertTest.Valid("0X3", (byte)3); + yield return ConvertTest.Valid(" 0X3 ", (byte)3); + yield return ConvertTest.Valid("&h4", (byte)4); + yield return ConvertTest.Valid("&H4", (byte)4); + yield return ConvertTest.Valid(" &H4 ", (byte)4); + yield return ConvertTest.Valid("+5", (byte)5); + yield return ConvertTest.Valid(" +5 ", (byte)5); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => ByteConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "8.0")); - } + yield return ConvertTest.Valid("!1", (byte)1, new CustomPositiveSymbolCulture()); - [Fact] - public static void ConvertTo_WithContext() - { - ConvertTo_WithContext(new object[3, 3] - { - { (byte)1, "1", null }, - { (byte)2, (byte)2, CultureInfo.InvariantCulture }, - { (byte)3, (float)3.0, null } - }, - ByteConverterTests.s_converter); + yield return ConvertTest.Throws("-1"); + yield return ConvertTest.Throws("256"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/CharConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/CharConverterTests.cs index e1251829cd2b..ef3a9b0f0352 100644 --- a/src/System.ComponentModel.TypeConverter/tests/CharConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/CharConverterTests.cs @@ -2,58 +2,40 @@ // 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.Collections; +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; +using System.Linq; using Xunit; namespace System.ComponentModel.Tests { - public class CharConverterTests : ConverterTestBase + public class CharConverterTests : TypeConverterTestBase { - private static TypeConverter s_converter = new CharConverter(); + public override TypeConverter Converter => new CharConverter(); - [Fact] - public static void CanConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - CanConvertFrom_WithContext(new object[2, 2] - { - { typeof(string), true }, - { typeof(int), false } - }, - CharConverterTests.s_converter); - } + yield return ConvertTest.Valid('a', "a"); + yield return ConvertTest.Valid('\u20AC', "\u20AC", CultureInfo.InvariantCulture); + yield return ConvertTest.Valid('\0', string.Empty); - [Fact] - public static void ConvertTo_WithContext() - { - ConvertTo_WithContext(new object[3, 3] - { - {'a', "a", null}, - {'\0', "", null}, - {'\u20AC', "\u20AC", CultureInfo.InvariantCulture} - }, - CharConverterTests.s_converter); + yield return ConvertTest.CantConvertTo('a', typeof(char)); + yield return ConvertTest.CantConvertTo('a', typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo('a', typeof(object)); } - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { " a ", 'a', CultureInfo.InvariantCulture }, - { " ", '\0', null}, - { "", '\0', null } - }, - CharConverterTests.s_converter); - } + yield return ConvertTest.Valid(" a ", 'a'); + yield return ConvertTest.Valid(" ", '\0'); + yield return ConvertTest.Valid("", '\0'); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - Assert.Throws( - () => CharConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "aaa")); + yield return ConvertTest.Throws("aa"); - Assert.Throws( - () => CharConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, null)); + yield return ConvertTest.CantConvertFrom(1); + yield return ConvertTest.CantConvertFrom(null); } } -} +} \ No newline at end of file diff --git a/src/System.ComponentModel.TypeConverter/tests/CollectionConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/CollectionConverterTests.cs index b3d678059d0d..fb0d70c55dc7 100644 --- a/src/System.ComponentModel.TypeConverter/tests/CollectionConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/CollectionConverterTests.cs @@ -2,29 +2,59 @@ // 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.Globalization; -using Microsoft.DotNet.RemoteExecutor; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using Xunit; namespace System.ComponentModel.Tests { - public class CollectionConverterTests : ConverterTestBase + public class CollectionConverterTests : TypeConverterTestBase { - private static TypeConverter s_converter = new CollectionConverter(); + public override TypeConverter Converter => new CollectionConverter(); - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertToTestData() { - RemoteExecutor.Invoke(() => - { - CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture; - - ConvertTo_WithContext(new object[1, 3] - { - { new Collection1(), "(Collection)", null } - }, - CollectionConverterTests.s_converter); - }).Dispose(); + yield return ConvertTest.Valid(new CustomCollection(), "(Collection)").WithInvariantRemoteInvokeCulture(); + yield return ConvertTest.Valid(1, "1"); + + yield return ConvertTest.CantConvertTo(new CustomCollection(), typeof(CustomCollection)); + yield return ConvertTest.CantConvertTo(new CustomCollection(), typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo(new CustomCollection(), typeof(object)); + } + + public override IEnumerable ConvertFromTestData() + { + yield return ConvertTest.CantConvertFrom(new CustomCollection()); + } + + public static IEnumerable GetProperties_TestData() + { + yield return new object[] { null }; + yield return new object[] { new CustomCollection() }; + } + + [Theory] + [MemberData(nameof(GetProperties_TestData))] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Returns null in .NET Framework")] + public void GetProperties_Invoke_ReturnsEmpty(object value) + { + PropertyDescriptorCollection properties = Converter.GetProperties(value); + Assert.Empty(properties); + } + + [Serializable] + public class CustomCollection : ICollection + { + public void CopyTo(Array array, int index) => throw new NotImplementedException(); + + public int Count => throw new NotImplementedException(); + + public bool IsSynchronized => throw new NotImplementedException(); + + public object SyncRoot => throw new NotImplementedException(); + + public IEnumerator GetEnumerator() => throw new NotImplementedException(); } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/DecimalConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/DecimalConverterTests.cs index 590d59f62f6e..5a2a9ba890cd 100644 --- a/src/System.ComponentModel.TypeConverter/tests/DecimalConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/DecimalConverterTests.cs @@ -2,43 +2,57 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class DecimalConverterTests : ConverterTestBase + public class DecimalConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new DecimalConverter(); + public override TypeConverter Converter => new DecimalConverter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[2, 3] - { - { 1.1m + " ", (decimal)1.1, null }, - { "+7", (decimal)7, CultureInfo.InvariantCulture } - }, - DecimalConverterTests.s_converter); - } + yield return ConvertTest.Valid((decimal)-1, "-1"); + yield return ConvertTest.Valid(1.1m, 1.1m.ToString()); + yield return ConvertTest.Valid(3.3m, (float)3.3); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => DecimalConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "0x8")); + yield return ConvertTest.Valid((decimal)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.Valid((decimal)3, new InstanceDescriptor( + typeof(decimal).GetConstructor(new Type[] { typeof(int[]) }), + new object[] { new int[] { 3, 0, 0, 0 } } + )); + + yield return ConvertTest.CantConvertTo((decimal)3, typeof(decimal)); + yield return ConvertTest.CantConvertTo((decimal)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - {(decimal)1.1, 1.1m.ToString(), null}, - {(decimal)1.1, (byte)1, CultureInfo.InvariantCulture}, - {(decimal)1.1, (float)1.1, null} - }, - DecimalConverterTests.s_converter); + yield return ConvertTest.Valid("1", (decimal)1); + yield return ConvertTest.Valid(1.1.ToString(), 1.1m); + yield return ConvertTest.Valid(" -1 ", (decimal)-1); + yield return ConvertTest.Valid("+5", (decimal)5); + yield return ConvertTest.Valid(" +5 ", (decimal)5); + + yield return ConvertTest.Throws("#2"); + yield return ConvertTest.Throws(" #2 "); + yield return ConvertTest.Throws("0x3"); + if (!PlatformDetection.IsFullFramework) + { + yield return ConvertTest.Throws("0X3"); + yield return ConvertTest.Throws(" 0X3 "); + yield return ConvertTest.Throws("&h4"); + yield return ConvertTest.Throws("&H4"); + yield return ConvertTest.Throws(" &H4 "); + } + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/DoubleConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/DoubleConverterTests.cs index 52d9751b3a82..7b7a61660d32 100644 --- a/src/System.ComponentModel.TypeConverter/tests/DoubleConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/DoubleConverterTests.cs @@ -2,43 +2,53 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class DoubleConverterTests : ConverterTestBase + public class DoubleConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new DoubleConverter(); + public override TypeConverter Converter => new DoubleConverter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[2, 3] - { - { 1.1 + " ", (double)1.1, null }, - { "+7", (double)7, CultureInfo.InvariantCulture } - }, - DoubleConverterTests.s_converter); - } + yield return ConvertTest.Valid((double)-1, "-1"); + yield return ConvertTest.Valid(1.1, 1.1.ToString()); + yield return ConvertTest.Valid((double)2, (double)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((double)3.1, (float)3.1); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => DoubleConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "0x8")); + yield return ConvertTest.Valid((double)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo((double)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((double)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - { (double)1.1, 1.1.ToString(), null }, - { (double)1.1, (float)1.1, CultureInfo.InvariantCulture }, - { (double)1.1, (float)1.1, null } - }, - DoubleConverterTests.s_converter); + yield return ConvertTest.Valid("1", (double)1); + yield return ConvertTest.Valid(1.1.ToString(), 1.1); + yield return ConvertTest.Valid(" -1 ", (double)-1); + yield return ConvertTest.Valid("+5", (double)5); + yield return ConvertTest.Valid(" +5 ", (double)5); + + yield return ConvertTest.Throws("#2"); + yield return ConvertTest.Throws(" #2 "); + yield return ConvertTest.Throws("0x3"); + if (!PlatformDetection.IsFullFramework) + { + yield return ConvertTest.Throws("0X3"); + yield return ConvertTest.Throws(" 0X3 "); + yield return ConvertTest.Throws("&h4"); + yield return ConvertTest.Throws("&H4"); + yield return ConvertTest.Throws(" &H4 "); + } + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/Int16ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/Int16ConverterTests.cs index 2b40e3e0a98e..6e6093cbbb55 100644 --- a/src/System.ComponentModel.TypeConverter/tests/Int16ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/Int16ConverterTests.cs @@ -3,11 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; namespace System.ComponentModel.Tests { - public class Int16ConverterTests : TypeConverterTestBase + public class Int16ConverterTests : BaseNumberConverterTests { public override TypeConverter Converter => new Int16Converter(); @@ -16,23 +17,35 @@ public override IEnumerable ConvertToTestData() yield return ConvertTest.Valid((short)-1, "-1"); yield return ConvertTest.Valid((short)2, (short)2, CultureInfo.InvariantCulture); yield return ConvertTest.Valid((short)3, (float)3.0); + + yield return ConvertTest.Valid((short)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo((short)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((short)3, typeof(object)); } public override IEnumerable ConvertFromTestData() { yield return ConvertTest.Valid("1", (short)1); - yield return ConvertTest.Valid("-1 ", (short)-1); + yield return ConvertTest.Valid(" -1 ", (short)-1); yield return ConvertTest.Valid("#2", (short)2); - yield return ConvertTest.Valid("+7", (short)7); - - yield return ConvertTest.Throws("8.0"); - yield return ConvertTest.Throws(""); - yield return ConvertTest.Throws("bad"); + yield return ConvertTest.Valid(" #2 ", (short)2); + yield return ConvertTest.Valid("0x3", (short)3); + yield return ConvertTest.Valid("0X3", (short)3); + yield return ConvertTest.Valid(" 0X3 ", (short)3); + yield return ConvertTest.Valid("&h4", (short)4); + yield return ConvertTest.Valid("&H4", (short)4); + yield return ConvertTest.Valid(" &H4 ", (short)4); + yield return ConvertTest.Valid("+5", (short)5); + yield return ConvertTest.Valid(" +5 ", (short)5); yield return ConvertTest.Throws("32768"); yield return ConvertTest.Throws("-32769"); - yield return ConvertTest.CantConvert(new object()); + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/Int32ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/Int32ConverterTests.cs index 1a4288c7e3d5..65446b709dd8 100644 --- a/src/System.ComponentModel.TypeConverter/tests/Int32ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/Int32ConverterTests.cs @@ -2,44 +2,50 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class Int32ConverterTests : ConverterTestBase + public class Int32ConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new Int32Converter(); + public override TypeConverter Converter => new Int32Converter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (int)1, null }, - { "#2", (int)2, null }, - { "+7", (int)7, CultureInfo.InvariantCulture } - }, - Int32ConverterTests.s_converter); - } + yield return ConvertTest.Valid(-1, "-1"); + yield return ConvertTest.Valid(2, 2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid(3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => Int32ConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "8.0")); + yield return ConvertTest.Valid(-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo(3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo(3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - {(int)1, "1", null}, - {(int)(-2), (int)(-2), CultureInfo.InvariantCulture}, - {(int)3, (float)3.0, null} - }, - Int32ConverterTests.s_converter); + yield return ConvertTest.Valid("1", 1); + yield return ConvertTest.Valid(" -1 ", -1); + yield return ConvertTest.Valid("#2", 2); + yield return ConvertTest.Valid(" #2 ", 2); + yield return ConvertTest.Valid("0x3", 3); + yield return ConvertTest.Valid("0X3", 3); + yield return ConvertTest.Valid(" 0X3 ", 3); + yield return ConvertTest.Valid("&h4", 4); + yield return ConvertTest.Valid("&H4", 4); + yield return ConvertTest.Valid(" &H4 ", 4); + yield return ConvertTest.Valid("+5", 5); + yield return ConvertTest.Valid(" +5 ", 5); + + yield return ConvertTest.Throws("2147483648"); + yield return ConvertTest.Throws("-2147483649"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/Int64ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/Int64ConverterTests.cs index ec8862430bbc..2e033baf34d8 100644 --- a/src/System.ComponentModel.TypeConverter/tests/Int64ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/Int64ConverterTests.cs @@ -2,44 +2,50 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class Int64ConverterTests : ConverterTestBase + public class Int64ConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new Int64Converter(); + public override TypeConverter Converter => new Int64Converter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (long)1, null }, - { "#2", (long)2, null }, - { "+7", (long)7, CultureInfo.InvariantCulture } - }, - Int64ConverterTests.s_converter); - } + yield return ConvertTest.Valid((long)-1, "-1"); + yield return ConvertTest.Valid((long)2, (long)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((long)3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => Int64ConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "8.0")); + yield return ConvertTest.Valid((long)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo((long)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((long)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - {(long)1, "1", null}, - {(long)(-2), (long)(-2), CultureInfo.InvariantCulture}, - {(long)3, (float)3.0, null} - }, - Int64ConverterTests.s_converter); + yield return ConvertTest.Valid("1", (long)1); + yield return ConvertTest.Valid(" -1 ", (long)-1); + yield return ConvertTest.Valid("#2", (long)2); + yield return ConvertTest.Valid(" #2 ", (long)2); + yield return ConvertTest.Valid("0x3", (long)3); + yield return ConvertTest.Valid("0X3", (long)3); + yield return ConvertTest.Valid(" 0X3 ", (long)3); + yield return ConvertTest.Valid("&h4", (long)4); + yield return ConvertTest.Valid("&H4", (long)4); + yield return ConvertTest.Valid(" &H4 ", (long)4); + yield return ConvertTest.Valid("+5", (long)5); + yield return ConvertTest.Valid(" +5 ", (long)5); + + yield return ConvertTest.Throws("9223372036854775808"); + yield return ConvertTest.Throws("-9223372036854775809"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/SByteConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/SByteConverterTests.cs index 0353042ff827..59493f10b6c6 100644 --- a/src/System.ComponentModel.TypeConverter/tests/SByteConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/SByteConverterTests.cs @@ -2,44 +2,52 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class SByteConverterTests : ConverterTestBase + public class SByteConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new SByteConverter(); + public override TypeConverter Converter => new SByteConverter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (sbyte)1, null }, - { "&H6", (sbyte)6, null }, - { "-7", (sbyte)(-7), CultureInfo.InvariantCulture } - }, - SByteConverterTests.s_converter); - } + yield return ConvertTest.Valid((sbyte)-1, "-1"); + yield return ConvertTest.Valid((sbyte)2, (sbyte)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((sbyte)3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => SByteConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "8.0")); + yield return ConvertTest.Valid((sbyte)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo((sbyte)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((sbyte)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - { (sbyte)1, "1", null }, - { (sbyte)(-2), (sbyte)(-2), CultureInfo.InvariantCulture }, - { (sbyte)3, (float)3.0, null } - }, - SByteConverterTests.s_converter); + yield return ConvertTest.Valid("1", (sbyte)1); + yield return ConvertTest.Valid(" -1 ", (sbyte)-1); + yield return ConvertTest.Valid("#2", (sbyte)2); + yield return ConvertTest.Valid(" #2 ", (sbyte)2); + yield return ConvertTest.Valid("0x3", (sbyte)3); + yield return ConvertTest.Valid("0X3", (sbyte)3); + yield return ConvertTest.Valid(" 0X3 ", (sbyte)3); + yield return ConvertTest.Valid("&h4", (sbyte)4); + yield return ConvertTest.Valid("&H4", (sbyte)4); + yield return ConvertTest.Valid(" &H4 ", (sbyte)4); + yield return ConvertTest.Valid("+5", (sbyte)5); + yield return ConvertTest.Valid(" +5 ", (sbyte)5); + + yield return ConvertTest.Valid("!1", (sbyte)1, new CustomPositiveSymbolCulture()); + + yield return ConvertTest.Throws("128"); + yield return ConvertTest.Throws("-129"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/SampleClasses.cs b/src/System.ComponentModel.TypeConverter/tests/SampleClasses.cs index d7b4dc935ab5..8a33f2904871 100644 --- a/src/System.ComponentModel.TypeConverter/tests/SampleClasses.cs +++ b/src/System.ComponentModel.TypeConverter/tests/SampleClasses.cs @@ -60,35 +60,6 @@ public string ToString(string format, IFormatProvider formatProvider) public const string Token = "Formatted class."; } - [Serializable] - public class Collection1 : ICollection - { - public void CopyTo(Array array, int index) - { - throw new NotImplementedException(); - } - - public int Count - { - get { throw new NotImplementedException(); } - } - - public bool IsSynchronized - { - get { throw new NotImplementedException(); } - } - - public object SyncRoot - { - get { throw new NotImplementedException(); } - } - - public IEnumerator GetEnumerator() - { - throw new NotImplementedException(); - } - } - public class MyTypeListConverter : TypeListConverter { public MyTypeListConverter(Type[] types) diff --git a/src/System.ComponentModel.TypeConverter/tests/SingleConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/SingleConverterTests.cs index b35a7c8a7a37..f0528fb725b9 100644 --- a/src/System.ComponentModel.TypeConverter/tests/SingleConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/SingleConverterTests.cs @@ -2,42 +2,53 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class SingleConverterTests : ConverterTestBase + public class SingleConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new SingleConverter(); + public override TypeConverter Converter => new SingleConverter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[2, 3] - { - { 1.1f + " ", (float)1.1, null }, - { "+7", (float)7, CultureInfo.InvariantCulture } - }, - SingleConverterTests.s_converter); - } + yield return ConvertTest.Valid((float)-1, "-1"); + yield return ConvertTest.Valid(1.1f, 1.1f.ToString()); + yield return ConvertTest.Valid((float)2, (float)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((float)3, (double)3); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => SingleConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "0x8")); + yield return ConvertTest.Valid((float)-1, "?1", new CustomPositiveSymbolCulture()); + + yield return ConvertTest.CantConvertTo((float)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((float)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[2, 3] - { - { (float)1.1, 1.1f.ToString(), null }, - { (float)1.1, 1, CultureInfo.InvariantCulture } - }, - SingleConverterTests.s_converter); + yield return ConvertTest.Valid("1", (float)1); + yield return ConvertTest.Valid(1.1.ToString(), 1.1f); + yield return ConvertTest.Valid(" -1 ", (float)-1); + yield return ConvertTest.Valid("+5", (float)5); + yield return ConvertTest.Valid(" +5 ", (float)5); + + yield return ConvertTest.Throws("#2"); + yield return ConvertTest.Throws(" #2 "); + yield return ConvertTest.Throws("0x3"); + if (!PlatformDetection.IsFullFramework) + { + yield return ConvertTest.Throws("0X3"); + yield return ConvertTest.Throws(" 0X3 "); + yield return ConvertTest.Throws("&h4"); + yield return ConvertTest.Throws("&H4"); + yield return ConvertTest.Throws(" &H4 "); + } + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj b/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj index 234129438055..27fa5d36bb71 100644 --- a/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj +++ b/src/System.ComponentModel.TypeConverter/tests/System.ComponentModel.TypeConverter.Tests.csproj @@ -45,6 +45,7 @@ + @@ -150,7 +151,7 @@ - + %(RecursiveDir)%(Filename)%(Extension) diff --git a/src/System.ComponentModel.TypeConverter/tests/TypeConverterTestBase.cs b/src/System.ComponentModel.TypeConverter/tests/TypeConverterTestBase.cs index 39ff28f0a19b..f2082ea4df68 100644 --- a/src/System.ComponentModel.TypeConverter/tests/TypeConverterTestBase.cs +++ b/src/System.ComponentModel.TypeConverter/tests/TypeConverterTestBase.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -38,7 +39,7 @@ public void ConvertTo_DestinationType_Success() if (convertTest.CanConvert) { object actual = Converter.ConvertTo(convertTest.Context, convertTest.Culture, convertTest.Source, convertTest.DestinationType); - Assert.Equal(convertTest.Expected, actual); + AssertEqualInstanceDescriptor(convertTest.Expected, actual); } else { @@ -78,6 +79,13 @@ public void ConvertTo_NullDestinationType_ThrowsArgumentNullException() AssertExtensions.Throws("destinationType", () => Converter.ConvertTo(TypeConverterTests.s_context, null, "", null)); } + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Core fixes some NullReferenceExceptions in CanConvertTo")] + public void CanConvertTo_NullDestinationType_ReturnsFalse() + { + Assert.False(Converter.CanConvertTo(null)); + } + [Fact] public void ConvertFrom_DestinationType_Success() { @@ -153,6 +161,20 @@ public void GetStandardValuesExclusive_Invoke_ReturnsExpected() Assert.Equal(StandardValuesExclusive, converter.GetStandardValuesExclusive()); } + private static void AssertEqualInstanceDescriptor(object expected, object actual) + { + if (expected is InstanceDescriptor expectedDescriptor && actual is InstanceDescriptor actualDescriptor) + { + Assert.Equal(expectedDescriptor.MemberInfo, actualDescriptor.MemberInfo); + Assert.Equal(expectedDescriptor.Arguments, actualDescriptor.Arguments); + Assert.Equal(expectedDescriptor.IsComplete, actualDescriptor.IsComplete); + } + else + { + Assert.Equal(expected, actual); + } + } + [Serializable] public class ConvertTest : ISerializable { @@ -196,7 +218,7 @@ public static ConvertTest Throws(obje }; } - public static ConvertTest CantConvert(object source, Type destinationType = null, CultureInfo culture = null) + public static ConvertTest CantConvertTo(object source, Type destinationType = null, CultureInfo culture = null) { return new ConvertTest { @@ -209,6 +231,18 @@ public static ConvertTest CantConvert(object source, Type destinationType = null }; } + public static ConvertTest CantConvertFrom(object source, CultureInfo culture = null) + { + return new ConvertTest + { + Source = source, + Culture = culture, + NetCoreExceptionType = typeof(NotSupportedException), + NetFrameworkExceptionType = typeof(NotSupportedException), + CanConvert = false + }; + } + public ConvertTest WithContext(ITypeDescriptorContext context) { Context = context; diff --git a/src/System.ComponentModel.TypeConverter/tests/UInt16ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/UInt16ConverterTests.cs index b8aa2076b9d1..b901569e97b9 100644 --- a/src/System.ComponentModel.TypeConverter/tests/UInt16ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/UInt16ConverterTests.cs @@ -2,51 +2,49 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class UInt16ConverterTests : ConverterTestBase + public class UInt16ConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new UInt16Converter(); + public override TypeConverter Converter => new UInt16Converter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (ushort)1, null }, - { "#2", (ushort)2, null }, - { "+7", (ushort)7, CultureInfo.InvariantCulture } - }, - UInt16ConverterTests.s_converter); - } + yield return ConvertTest.Valid((ushort)1, "1"); + yield return ConvertTest.Valid((ushort)2, (ushort)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((ushort)3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => UInt16ConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "-8")); + yield return ConvertTest.CantConvertTo((ushort)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((ushort)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - { (ushort)1, "1", null }, - { (ushort)2, (ushort)2, CultureInfo.InvariantCulture }, - { (ushort)3, (float)3.0, null } - }, - UInt16ConverterTests.s_converter); - } + yield return ConvertTest.Valid("1", (ushort)1); + yield return ConvertTest.Valid("#2", (ushort)2); + yield return ConvertTest.Valid(" #2 ", (ushort)2); + yield return ConvertTest.Valid("0x3", (ushort)3); + yield return ConvertTest.Valid("0X3", (ushort)3); + yield return ConvertTest.Valid(" 0X3 ", (ushort)3); + yield return ConvertTest.Valid("&h4", (ushort)4); + yield return ConvertTest.Valid("&H4", (ushort)4); + yield return ConvertTest.Valid(" &H4 ", (ushort)4); + yield return ConvertTest.Valid("+5", (ushort)5); + yield return ConvertTest.Valid(" +5 ", (ushort)5); - [Fact] - public static void ConvertFrom_InvalidValue_ExceptionMessageContainsTypeName() - { - Exception e = Assert.ThrowsAny(() => s_converter.ConvertFrom("badvalue")); - Assert.Contains(typeof(ushort).Name, e.Message); + yield return ConvertTest.Valid("!1", (ushort)1, new CustomPositiveSymbolCulture()); + + yield return ConvertTest.Throws("-1"); + yield return ConvertTest.Throws("65536"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/UInt32ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/UInt32ConverterTests.cs index 198405310d29..1fa7e74ebf7b 100644 --- a/src/System.ComponentModel.TypeConverter/tests/UInt32ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/UInt32ConverterTests.cs @@ -2,44 +2,49 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class UInt32ConverterTests : ConverterTestBase + public class UInt32ConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new UInt32Converter(); + public override TypeConverter Converter => new UInt32Converter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (uint)1, null }, - { "#2", (uint)2, null }, - { "+7", (uint)7, CultureInfo.InvariantCulture } - }, - UInt32ConverterTests.s_converter); - } + yield return ConvertTest.Valid((uint)1, "1"); + yield return ConvertTest.Valid((uint)2, (uint)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((uint)3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => UInt32ConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "-8")); + yield return ConvertTest.CantConvertTo((uint)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((uint)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - { (uint)1, "1", null }, - { (uint)2, (uint)2, CultureInfo.InvariantCulture }, - { (uint)3, (float)3.0, null } - }, - UInt32ConverterTests.s_converter); + yield return ConvertTest.Valid("1", (uint)1); + yield return ConvertTest.Valid("#2", (uint)2); + yield return ConvertTest.Valid(" #2 ", (uint)2); + yield return ConvertTest.Valid("0x3", (uint)3); + yield return ConvertTest.Valid("0X3", (uint)3); + yield return ConvertTest.Valid(" 0X3 ", (uint)3); + yield return ConvertTest.Valid("&h4", (uint)4); + yield return ConvertTest.Valid("&H4", (uint)4); + yield return ConvertTest.Valid(" &H4 ", (uint)4); + yield return ConvertTest.Valid("+5", (uint)5); + yield return ConvertTest.Valid(" +5 ", (uint)5); + + yield return ConvertTest.Valid("!1", (uint)1, new CustomPositiveSymbolCulture()); + + yield return ConvertTest.Throws("-1"); + yield return ConvertTest.Throws("4294967296"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.ComponentModel.TypeConverter/tests/UInt64ConverterTests.cs b/src/System.ComponentModel.TypeConverter/tests/UInt64ConverterTests.cs index 49d39c0868ee..dc96ae0eabe5 100644 --- a/src/System.ComponentModel.TypeConverter/tests/UInt64ConverterTests.cs +++ b/src/System.ComponentModel.TypeConverter/tests/UInt64ConverterTests.cs @@ -2,44 +2,49 @@ // 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.Collections.Generic; +using System.ComponentModel.Design.Serialization; using System.Globalization; -using Xunit; namespace System.ComponentModel.Tests { - public class UInt64ConverterTests : ConverterTestBase + public class UInt64ConverterTests : BaseNumberConverterTests { - private static TypeConverter s_converter = new UInt64Converter(); + public override TypeConverter Converter => new UInt64Converter(); - [Fact] - public static void ConvertFrom_WithContext() + public override IEnumerable ConvertToTestData() { - ConvertFrom_WithContext(new object[3, 3] - { - { "1 ", (ulong)1, null }, - { "#2", (ulong)2, null }, - { "+7", (ulong)7, CultureInfo.InvariantCulture } - }, - UInt64ConverterTests.s_converter); - } + yield return ConvertTest.Valid((ulong)1, "1"); + yield return ConvertTest.Valid((ulong)2, (ulong)2, CultureInfo.InvariantCulture); + yield return ConvertTest.Valid((ulong)3, (float)3.0); - [Fact] - public static void ConvertFrom_WithContext_Negative() - { - AssertExtensions.Throws( - () => UInt64ConverterTests.s_converter.ConvertFrom(TypeConverterTests.s_context, null, "-8")); + yield return ConvertTest.CantConvertTo((ulong)3, typeof(InstanceDescriptor)); + yield return ConvertTest.CantConvertTo((ulong)3, typeof(object)); } - [Fact] - public static void ConvertTo_WithContext() + public override IEnumerable ConvertFromTestData() { - ConvertTo_WithContext(new object[3, 3] - { - { (ulong)1, "1", null }, - { (ulong)2, (ulong)2, CultureInfo.InvariantCulture }, - { (ulong)3, (float)3.0, null } - }, - UInt64ConverterTests.s_converter); + yield return ConvertTest.Valid("1", (ulong)1); + yield return ConvertTest.Valid("#2", (ulong)2); + yield return ConvertTest.Valid(" #2 ", (ulong)2); + yield return ConvertTest.Valid("0x3", (ulong)3); + yield return ConvertTest.Valid("0X3", (ulong)3); + yield return ConvertTest.Valid(" 0X3 ", (ulong)3); + yield return ConvertTest.Valid("&h4", (ulong)4); + yield return ConvertTest.Valid("&H4", (ulong)4); + yield return ConvertTest.Valid(" &H4 ", (ulong)4); + yield return ConvertTest.Valid("+5", (ulong)5); + yield return ConvertTest.Valid(" +5 ", (ulong)5); + + yield return ConvertTest.Valid("!1", (ulong)1, new CustomPositiveSymbolCulture()); + + yield return ConvertTest.Throws("-1"); + yield return ConvertTest.Throws("18446744073709551616"); + + foreach (ConvertTest test in base.ConvertFromTestData()) + { + yield return test; + } } } } diff --git a/src/System.Data.Odbc/src/Configurations.props b/src/System.Data.Odbc/src/Configurations.props index 3430266fd638..0e296fd500aa 100644 --- a/src/System.Data.Odbc/src/Configurations.props +++ b/src/System.Data.Odbc/src/Configurations.props @@ -14,7 +14,6 @@ netcoreapp-Linux; netcoreapp-OSX; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netfx-Windows_NT; diff --git a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj index b74ad0b506d8..f3cc82e709c3 100644 --- a/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj +++ b/src/System.Data.Odbc/tests/System.Data.Odbc.Tests.csproj @@ -20,7 +20,7 @@ Common\Interop\Unix\Interop.Libraries.cs - + Common\Interop\Unix\libdl\Interop.dlopen.cs diff --git a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj index ad70b88d5c50..a730f2e82f2a 100644 --- a/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj +++ b/src/System.Data.SqlClient/src/System.Data.SqlClient.csproj @@ -243,6 +243,9 @@ Common\Interop\Windows\Interop.UNICODE_STRING.cs + + Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + Common\Interop\Windows\Kernel32\Interop.IoControlCodeAccess.cs diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsConnection.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsConnection.cs index 989e9a2b94a9..33e139e3048f 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsConnection.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsConnection.cs @@ -44,7 +44,7 @@ public SNIMarsConnection(SNIHandle lowerHandle) _lowerHandle.SetAsyncCallbacks(HandleReceiveComplete, HandleSendComplete); } - public SNIMarsHandle CreateMarsSession(object callbackObject, bool async) + public SNIMarsHandle CreateMarsSession(TdsParserStateObject callbackObject, bool async) { lock (this) { @@ -126,12 +126,12 @@ public uint CheckConnection() /// /// Process a receive error /// - public void HandleReceiveError(SNIPacket packet) + public void HandleReceiveError(SNIPacket packet, uint sniErrorCode) { Debug.Assert(Monitor.IsEntered(this), "HandleReceiveError was called without being locked."); foreach (SNIMarsHandle handle in _sessions.Values) { - handle.HandleReceiveError(packet); + handle.HandleReceiveError(packet, sniErrorCode); } packet?.Dispose(); } @@ -161,7 +161,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) { lock (this) { - HandleReceiveError(packet); + HandleReceiveError(packet, sniErrorCode); return; } } @@ -192,7 +192,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) return; } - HandleReceiveError(packet); + HandleReceiveError(packet, sniErrorCode); return; } } @@ -223,7 +223,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) return; } - HandleReceiveError(packet); + HandleReceiveError(packet, sniErrorCode); return; } } @@ -234,7 +234,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) if (!_sessions.ContainsKey(_currentHeader.sessionId)) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.SMUX_PROV, 0, SNICommon.InvalidParameterError, string.Empty); - HandleReceiveError(packet); + HandleReceiveError(packet, sniErrorCode); _lowerHandle.Dispose(); _lowerHandle = null; return; @@ -280,7 +280,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) return; } - HandleReceiveError(packet); + HandleReceiveError(packet, sniErrorCode); return; } } diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsHandle.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsHandle.cs index 6ba903947386..77b48fed10ff 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsHandle.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIMarsHandle.cs @@ -19,7 +19,7 @@ internal class SNIMarsHandle : SNIHandle private readonly uint _status = TdsEnums.SNI_UNINITIALIZED; private readonly Queue _receivedPacketQueue = new Queue(); private readonly Queue _sendPacketQueue = new Queue(); - private readonly object _callbackObject; + private readonly TdsParserStateObject _callbackObject; private readonly Guid _connectionId = Guid.NewGuid(); private readonly ushort _sessionId; private readonly ManualResetEventSlim _packetEvent = new ManualResetEventSlim(false); @@ -78,7 +78,7 @@ public override void Dispose() /// MARS session ID /// Callback object /// true if connection is asynchronous - public SNIMarsHandle(SNIMarsConnection connection, ushort sessionId, object callbackObject, bool async) + public SNIMarsHandle(SNIMarsConnection connection, ushort sessionId, TdsParserStateObject callbackObject, bool async) { _sessionId = sessionId; _connection = connection; @@ -295,15 +295,23 @@ public override uint ReceiveAsync(ref SNIPacket packet) /// /// Handle receive error /// - public void HandleReceiveError(SNIPacket packet) + public void HandleReceiveError(SNIPacket packet, uint sniErrorCode) { lock (_receivedPacketQueue) { _connectionError = SNILoadHandle.SingletonInstance.LastError; _packetEvent.Set(); } - - ((TdsParserStateObject)_callbackObject).ReadAsyncCallback(PacketHandle.FromManagedPacket(packet), 1); + if (sniErrorCode == TdsEnums.SNI_WSAECONNRESET) + { + TdsParser parser = _callbackObject.Parser; + parser.State = TdsParserState.Broken; + parser.Connection.BreakConnection(); + } + else + { + _callbackObject.ReadAsyncCallback(PacketHandle.FromManagedPacket(packet), sniErrorCode); + } } /// @@ -317,7 +325,7 @@ public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) { Debug.Assert(_callbackObject != null); - ((TdsParserStateObject)_callbackObject).WriteAsyncCallback(PacketHandle.FromManagedPacket(packet), sniErrorCode); + _callbackObject.WriteAsyncCallback(PacketHandle.FromManagedPacket(packet), sniErrorCode); } } diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetCoreApp.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetCoreApp.cs index 6e5cab47e390..0e353b04af79 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetCoreApp.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetCoreApp.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.IO; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -18,31 +19,39 @@ internal partial class SNIPacket /// Completion callback public void ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback) { - // Treat local function as a static and pass all params otherwise as async will allocate - async Task ReadFromStreamAsync(SNIPacket packet, SNIAsyncCallback cb, ValueTask valueTask) + static async Task ReadFromStreamAsync(SNIPacket packet, SNIAsyncCallback cb, ValueTask valueTask) { - bool error = false; + uint errorCode = TdsEnums.SNI_SUCCESS; try { packet._length = await valueTask.ConfigureAwait(false); if (packet._length == 0) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, 0, SNICommon.ConnTerminatedError, string.Empty); - error = true; + errorCode = TdsEnums.SNI_WSAECONNRESET; } } + catch (IOException ioException) when ( + ioException?.InnerException is SocketException socketException && + socketException!=null && + socketException.SocketErrorCode==SocketError.OperationAborted + ) + { + SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, socketException); + errorCode = TdsEnums.SNI_WSAECONNRESET; + } catch (Exception ex) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, ex); - error = true; + errorCode = TdsEnums.SNI_ERROR; } - if (error) + if (errorCode != TdsEnums.SNI_SUCCESS) { packet.Release(); } - cb(packet, error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS); + cb(packet, errorCode); } ValueTask vt = stream.ReadAsync(new Memory(_data, 0, _capacity), CancellationToken.None); @@ -70,8 +79,7 @@ async Task ReadFromStreamAsync(SNIPacket packet, SNIAsyncCallback cb, ValueTask< /// Stream to write to public void WriteToStreamAsync(Stream stream, SNIAsyncCallback callback, SNIProviders provider, bool disposeAfterWriteAsync = false) { - // Treat local function as a static and pass all params otherwise as async will allocate - async Task WriteToStreamAsync(SNIPacket packet, SNIAsyncCallback cb, SNIProviders providers, bool disposeAfter, ValueTask valueTask) + static async Task WriteToStreamAsync(SNIPacket packet, SNIAsyncCallback cb, SNIProviders providers, bool disposeAfter, ValueTask valueTask) { uint status = TdsEnums.SNI_SUCCESS; try diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetStandard.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetStandard.cs index bfa48ac17b61..106613099233 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetStandard.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/SNI/SNIPacket.NetStandard.cs @@ -4,6 +4,7 @@ using System.Buffers; using System.IO; +using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; @@ -18,31 +19,39 @@ internal partial class SNIPacket /// Completion callback public void ReadFromStreamAsync(Stream stream, SNIAsyncCallback callback) { - // Treat local function as a static and pass all params otherwise as async will allocate - async Task ReadFromStreamAsync(SNIPacket packet, SNIAsyncCallback cb, Task task) + static async Task ReadFromStreamAsync(SNIPacket packet, SNIAsyncCallback cb, Task task) { - bool error = false; + uint errorCode = TdsEnums.SNI_SUCCESS; try { packet._length = await task.ConfigureAwait(false); if (packet._length == 0) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, 0, SNICommon.ConnTerminatedError, string.Empty); - error = true; + errorCode = TdsEnums.SNI_WSAECONNRESET; } } + catch (IOException ioException) when ( + ioException?.InnerException is SocketException socketException && + socketException != null && + socketException.SocketErrorCode == SocketError.OperationAborted + ) + { + SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, socketException); + errorCode = TdsEnums.SNI_WSAECONNRESET; + } catch (Exception ex) { SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, ex); - error = true; + errorCode = TdsEnums.SNI_ERROR; } - if (error) + if (errorCode != TdsEnums.SNI_SUCCESS) { packet.Release(); } - cb(packet, error ? TdsEnums.SNI_ERROR : TdsEnums.SNI_SUCCESS); + cb(packet, errorCode); } Task t = stream.ReadAsync(_data, 0, _capacity, CancellationToken.None); diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsEnums.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsEnums.cs index 41e557bb95f7..11c17522ee01 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsEnums.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsEnums.cs @@ -538,7 +538,7 @@ public enum FedAuthLibrary : byte public const uint SNI_SUCCESS_IO_PENDING = 997; // Overlapped I/O operation is in progress. // Windows Sockets Error Codes - public const short SNI_WSAECONNRESET = 10054; // An existing connection was forcibly closed by the remote host. + public const uint SNI_WSAECONNRESET = 10054; // An existing connection was forcibly closed by the remote host. // SNI internal errors (shouldn't overlap with Win32 / socket errors) public const uint SNI_QUEUE_FULL = 1048576; // Packet queue is full diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs index 8e07c39cca1f..2c35b0de3a53 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParser.cs @@ -1937,7 +1937,7 @@ internal bool TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataRead } SqlEnvChange head = env; env = env.Next; - SqlEnvChangePool.Release(head); + head.Clear(); head = null; } break; @@ -2187,7 +2187,7 @@ private bool TryProcessEnvChange(int tokenLength, TdsParserStateObject stateObj, while (tokenLength > processedLength) { - SqlEnvChange env = SqlEnvChangePool.Allocate(); + var env = new SqlEnvChange(); if (!stateObj.TryReadByte(out env.type)) { diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs index 3b121ed1e7b6..96e1895c0ab8 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserHelperClasses.cs @@ -298,107 +298,6 @@ internal void Clear() } } - internal static class SqlEnvChangePool - { - private static int _count; - private static int _capacity; - private static SqlEnvChange[] _items; -#if DEBUG - private static string[] _stacks; -#endif - - static SqlEnvChangePool() - { - _capacity = 10; - _items = new SqlEnvChange[_capacity]; - _count = 1; -#if DEBUG - _stacks = new string[_capacity]; -#endif - } - - internal static SqlEnvChange Allocate() - { - SqlEnvChange retval = null; - lock (_items) - { - while (_count > 0 && retval is null) - { - int count = _count; // copy the count we think we have - int newCount = count - 1; // work out the new value we want - // exchange _count for newCount only if _count is the same as our cached copy - if (count == Interlocked.CompareExchange(ref _count, newCount, count)) - { - // count is now the previous value, we're the only thread that has it, so count-1 is safe to access - Interlocked.Exchange(ref retval, _items[count - 1]); - } - else - { - // otherwise the count wasn't what we expected, spin the while and try again. - } - } - } - if (retval is null) - { - retval = new SqlEnvChange(); - } - return retval; - } - - internal static void Release(SqlEnvChange item, [Runtime.CompilerServices.CallerMemberName] string caller = null) - { - if (item is null) - { - Debug.Fail("attenpting to release null packet"); - return; - } - item.Clear(); - { -#if DEBUG - if (_count > 0) - { - for (int index = 0; index < _count; index += 1) - { - if (object.ReferenceEquals(item, _items[index])) - { - Debug.Assert(false,$"releasing an item which already exists in the pool, count={_count}, index={index}, caller={caller}, released at={_stacks[index]}"); - } - } - } -#endif - int tries = 0; - - while (!(item is null) && tries < 3 && _count < _capacity) - { - int count = _count; - int newCount = count + 1; - if (count == Interlocked.CompareExchange(ref _count, newCount, count)) - { - _items[count] = item; -#if DEBUG - _stacks[count] = caller; -#endif - item = null; - } - else - { - tries += 1; - } - } - } - } - - internal static int Count => _count; - - internal static int Capacity => _capacity; - - internal static void Clear() - { - Array.Clear(_items, 0, _capacity); - _count = 0; - } - } - internal sealed class SqlLogin { internal int timeout; // login timeout diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs index a23ec191ad5d..653f6a0abc90 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObject.cs @@ -2704,7 +2704,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) // The mars physical connection can get a callback // with a packet but no result after the connection is closed. - if (source == null && _parser._pMarsPhysicalConObj == this) + if (source == null) { return; } diff --git a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectManaged.cs index 151d4e554aa0..d2742e51ecd3 100644 --- a/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/System.Data.SqlClient/src/System/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -47,7 +47,7 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect _sessionHandle = managedSNIObject.CreateMarsSession(this, async); } - internal SNIMarsHandle CreateMarsSession(object callbackObject, bool async) + internal SNIMarsHandle CreateMarsSession(TdsParserStateObject callbackObject, bool async) { return _marsConnection.CreateMarsSession(callbackObject, async); } diff --git a/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs b/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs index abdcff397891..a6edea508e54 100644 --- a/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs +++ b/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/ConnectionPoolTest.cs @@ -15,13 +15,13 @@ public static class ConnectionPoolTest private static readonly string _tcpMarsConnStr = (new SqlConnectionStringBuilder(DataTestUtility.TcpConnStr) { MultipleActiveResultSets = true, Pooling = true }).ConnectionString; - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)]: */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void ConnectionPool_NonMars() { RunDataTestForSingleConnString(_tcpConnStr); } - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void ConnectionPool_Mars() { RunDataTestForSingleConnString(_tcpMarsConnStr); diff --git a/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.netcoreapp.cs b/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.netcoreapp.cs index 45e0793e176e..1bd3db41a19f 100644 --- a/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.netcoreapp.cs +++ b/src/System.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.netcoreapp.cs @@ -21,7 +21,7 @@ public class PoolBlockPeriodTest private const int ConnectionTimeout = 15; private const int CompareMargin = 2; - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("Azure with Default Policy must Disable blocking (*.database.windows.net)", new object[] { AzureEndpointSample })] [InlineData("Azure with Default Policy must Disable blocking (*.database.chinacloudapi.cn)", new object[] { AzureChinaEnpointSample })] [InlineData("Azure with Default Policy must Disable blocking (*.database.usgovcloudapi.net)", new object[] { AzureUSGovernmentEndpointSample })] @@ -45,7 +45,7 @@ public void TestAzureBlockingPeriod(string description, object[] Params) PoolBlockingPeriodAzureTest(connString, policy); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("NonAzure with Default Policy must Enable blocking", new object[] { NonExistentServer })] [InlineData("NonAzure with Auto Policy must Enable Blocking", new object[] { NonExistentServer, PoolBlockingPeriod.Auto })] [InlineData("NonAzure with Always Policy must Enable Blocking", new object[] { NonExistentServer, PoolBlockingPeriod.AlwaysBlock })] @@ -66,7 +66,7 @@ public void TestNonAzureBlockingPeriod(string description, object[] Params) PoolBlockingPeriodNonAzureTest(connString, policy); } - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [InlineData("Test policy with Auto (lowercase)", "auto")] [InlineData("Test policy with Auto (PascalCase)", "Auto")] [InlineData("Test policy with Always (lowercase)", "alwaysblock")] diff --git a/src/System.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs b/src/System.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs index 5e160fbf52b0..6a0f1657bae3 100644 --- a/src/System.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/System.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -64,10 +64,10 @@ private static void RunAllTestsForSingleServer(string connectionString, bool usi // These tests fail with named pipes, since they try to do DNS lookups on named pipe paths. if (!usingNamePipes) { - //if (DataTestUtility.IsUsingNativeSNI()) /* [ActiveIssue(33930)] */ - //{ - // TimeoutDuringReadAsyncWithClosedReaderTest(connectionString); - //} + if (DataTestUtility.IsUsingNativeSNI()) + { + TimeoutDuringReadAsyncWithClosedReaderTest(connectionString); + } NonFatalTimeoutDuringRead(connectionString); } } diff --git a/src/System.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs b/src/System.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs index 2b1fee216b4b..0dba459eef28 100644 --- a/src/System.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs +++ b/src/System.Data.SqlClient/tests/ManualTests/SQL/MARSTest/MARSTest.cs @@ -33,7 +33,7 @@ public static void NamedPipesMARSTest() } #if DEBUG - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void MARSAsyncTimeoutTest() { using (SqlConnection connection = new SqlConnection(_connStr)) @@ -73,7 +73,7 @@ public static void MARSAsyncTimeoutTest() } } - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void MARSSyncTimeoutTest() { using (SqlConnection connection = new SqlConnection(_connStr)) diff --git a/src/System.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs b/src/System.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs index 0ea5a11c2235..00127b7fc963 100644 --- a/src/System.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs +++ b/src/System.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs @@ -17,7 +17,7 @@ namespace System.Data.SqlClient.ManualTesting.Tests public static class SqlCredentialTest { - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void CreateSqlConnectionWithCredential() { var user = "u" + Guid.NewGuid().ToString().Replace("-", ""); @@ -49,7 +49,7 @@ public static void CreateSqlConnectionWithCredential() } } - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void SqlConnectionChangePasswordPlaintext() { var user = "u" + Guid.NewGuid().ToString().Replace("-", ""); @@ -82,7 +82,7 @@ public static void SqlConnectionChangePasswordPlaintext() } } - [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility),nameof(DataTestUtility.AreConnStringsSetup))] public static void SqlConnectionChangePasswordSecureString() { var user = "u" + Guid.NewGuid().ToString().Replace("-", ""); @@ -122,7 +122,7 @@ public static void SqlConnectionChangePasswordSecureString() } } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), /* [ActiveIssue(33930)] */ nameof(DataTestUtility.IsUsingNativeSNI))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void OldCredentialsShouldFail() { String user = "u" + Guid.NewGuid().ToString().Replace("-", ""); diff --git a/src/System.Diagnostics.Contracts/src/Configurations.props b/src/System.Diagnostics.Contracts/src/Configurations.props index 8e16bc6010c4..a652fc326205 100644 --- a/src/System.Diagnostics.Contracts/src/Configurations.props +++ b/src/System.Diagnostics.Contracts/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Diagnostics.Contracts/src/System.Diagnostics.Contracts.csproj b/src/System.Diagnostics.Contracts/src/System.Diagnostics.Contracts.csproj index 0fb96cc17426..e56db2a96fd2 100644 --- a/src/System.Diagnostics.Contracts/src/System.Diagnostics.Contracts.csproj +++ b/src/System.Diagnostics.Contracts/src/System.Diagnostics.Contracts.csproj @@ -3,7 +3,7 @@ System.Diagnostics.Contracts true {13426B04-D1AC-4423-8519-F3EB44943B9D} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Diagnostics.Debug/src/Configurations.props b/src/System.Diagnostics.Debug/src/Configurations.props index 11c9656a5c73..dc04b5e626d2 100644 --- a/src/System.Diagnostics.Debug/src/Configurations.props +++ b/src/System.Diagnostics.Debug/src/Configurations.props @@ -3,8 +3,6 @@ netcoreapp-Unix; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj b/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj index 88008565388a..788c4bb1d818 100644 --- a/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj +++ b/src/System.Diagnostics.Debug/src/System.Diagnostics.Debug.csproj @@ -3,7 +3,7 @@ {E7E8DE8A-9EC1-46A8-A6EE-727DB32DBEB8} System.Diagnostics.Debug true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs b/src/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs deleted file mode 100644 index 83160f786763..000000000000 --- a/src/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeTokenHandle.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** Class: SafeTokenHandle -** -** A wrapper for a process handle -** -** -===========================================================*/ - -using System; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Security; - -namespace Microsoft.Win32.SafeHandles -{ - internal sealed class SafeTokenHandle : SafeHandle - { - private const int DefaultInvalidHandleValue = 0; - - internal static readonly SafeTokenHandle InvalidHandle = new SafeTokenHandle(new IntPtr(DefaultInvalidHandleValue)); - - internal SafeTokenHandle() : base(new IntPtr(DefaultInvalidHandleValue), true) { } - - internal SafeTokenHandle(IntPtr handle) - : base(new IntPtr(DefaultInvalidHandleValue), true) - { - SetHandle(handle); - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero || handle == new IntPtr(-1); } - } - - protected override bool ReleaseHandle() - { - return Interop.Kernel32.CloseHandle(handle); - } - } -} diff --git a/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj b/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj index 8a215fb97aa2..dcec78c65e50 100644 --- a/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj +++ b/src/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj @@ -86,6 +86,9 @@ + + Microsoft\Win32\SafeHandles\SafeTokenHandle.cs + Common\Interop\Windows\Interop.Libraries.cs @@ -274,7 +277,6 @@ - diff --git a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs index 3f76f52322b7..52ecef296efe 100644 --- a/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs +++ b/src/System.Diagnostics.Process/src/System/Diagnostics/Process.cs @@ -7,6 +7,8 @@ using System.ComponentModel; using System.Globalization; using System.IO; +using System.Reflection; +using System.Runtime.Serialization; using System.Text; using System.Threading; @@ -81,6 +83,10 @@ public partial class Process : Component internal bool _pendingOutputRead; internal bool _pendingErrorRead; + private static int s_cachedSerializationSwitch = 0; + delegate void ThrowIfDeserializationInProgressWithSwitchDel(string switchName, ref int cachedValue); + private static ThrowIfDeserializationInProgressWithSwitchDel s_throwIfDeserializationInProgressWithSwitch = CreateThrowIfDeserializationInProgressWithSwitchDelegate(); + /// /// /// Initializes a new instance of the class. @@ -1173,6 +1179,23 @@ private void SetProcessId(int processId) /// Additional optional configuration hook after a process ID is set. partial void ConfigureAfterProcessIdSet(); + /// + /// Builds a wrapper delegate for SerializationInfo.ThrowIfDeserializationInProgress(string, ref int), + /// since it is not exposed via contracts. + /// + private static ThrowIfDeserializationInProgressWithSwitchDel CreateThrowIfDeserializationInProgressWithSwitchDelegate() + { + ThrowIfDeserializationInProgressWithSwitchDel throwIfDeserializationInProgressDelegate = null; + MethodInfo throwMethod = typeof(SerializationInfo).GetMethod("ThrowIfDeserializationInProgress", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, null, new Type[] { typeof(string), typeof(int).MakeByRefType() }, new ParameterModifier[0]); + if (throwMethod != null) + { + throwIfDeserializationInProgressDelegate = (ThrowIfDeserializationInProgressWithSwitchDel)throwMethod.CreateDelegate(typeof(ThrowIfDeserializationInProgressWithSwitchDel)); + } + + return throwIfDeserializationInProgressDelegate; + } + /// /// /// Starts a process specified by the property of this @@ -1214,6 +1237,11 @@ public bool Start() throw new ObjectDisposedException(GetType().Name); } + if (s_throwIfDeserializationInProgressWithSwitch != null) + { + s_throwIfDeserializationInProgressWithSwitch("AllowProcessCreation", ref s_cachedSerializationSwitch); + } + return StartCore(startInfo); } diff --git a/src/System.Diagnostics.StackTrace/src/Configurations.props b/src/System.Diagnostics.StackTrace/src/Configurations.props index 46a01fa07ee1..0adb3f76b008 100644 --- a/src/System.Diagnostics.StackTrace/src/Configurations.props +++ b/src/System.Diagnostics.StackTrace/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; netcoreapp-Windows_NT; netcoreapp-Unix; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; diff --git a/src/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj b/src/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj index 9e4810245cac..dec6946da378 100644 --- a/src/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj +++ b/src/System.Diagnostics.StackTrace/src/System.Diagnostics.StackTrace.csproj @@ -6,7 +6,7 @@ true $(NoWarn);1685 - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Diagnostics.Tools/src/Configurations.props b/src/System.Diagnostics.Tools/src/Configurations.props index 7be6fd9cfa54..5f6eee24b03d 100644 --- a/src/System.Diagnostics.Tools/src/Configurations.props +++ b/src/System.Diagnostics.Tools/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Diagnostics.Tools/src/System.Diagnostics.Tools.csproj b/src/System.Diagnostics.Tools/src/System.Diagnostics.Tools.csproj index be368e35ef00..b429a0b434d9 100644 --- a/src/System.Diagnostics.Tools/src/System.Diagnostics.Tools.csproj +++ b/src/System.Diagnostics.Tools/src/System.Diagnostics.Tools.csproj @@ -4,7 +4,7 @@ System.Diagnostics.Tools true $(DefineConstants);SYSTEM_DIAGNOSTICS_TOOLS - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Diagnostics.TraceSource/src/Configurations.props b/src/System.Diagnostics.TraceSource/src/Configurations.props index 114d148a04de..7a5c957b97a7 100644 --- a/src/System.Diagnostics.TraceSource/src/Configurations.props +++ b/src/System.Diagnostics.TraceSource/src/Configurations.props @@ -6,6 +6,7 @@ netcoreapp-OSX; netcoreapp-Windows_NT; uap-Windows_NT; + uapaot-Windows_NT; diff --git a/src/System.Diagnostics.Tracing/src/Configurations.props b/src/System.Diagnostics.Tracing/src/Configurations.props index 7be6fd9cfa54..5f6eee24b03d 100644 --- a/src/System.Diagnostics.Tracing/src/Configurations.props +++ b/src/System.Diagnostics.Tracing/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj b/src/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj index 55fa1b22e71d..0d5e3eab46d5 100644 --- a/src/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj +++ b/src/System.Diagnostics.Tracing/src/System.Diagnostics.Tracing.csproj @@ -3,7 +3,7 @@ System.Diagnostics.Tracing {EB880FDC-326D-42B3-A3FD-0CD3BA29A7F4} true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Drawing.Common/src/System.Drawing.Common.csproj b/src/System.Drawing.Common/src/System.Drawing.Common.csproj index b94a8fb04bcd..776b14af4b97 100644 --- a/src/System.Drawing.Common/src/System.Drawing.Common.csproj +++ b/src/System.Drawing.Common/src/System.Drawing.Common.csproj @@ -337,6 +337,11 @@ Common\Interop\Unix\Interop.Libraries.cs + + placeholder.ico + + + Common\Interop\Unix\libdl\Interop.dlopen.cs @@ -346,9 +351,6 @@ Common\System\Runtime\InteropServices\FunctionWrapper.Unix.cs - - placeholder.ico - diff --git a/src/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs b/src/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs index c0489becafd0..5c956fc89fff 100644 --- a/src/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs +++ b/src/System.Drawing.Common/src/System/Drawing/GdiplusNative.Unix.cs @@ -32,7 +32,13 @@ private static IntPtr LoadNativeLibrary() if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { libraryName = "libgdiplus.dylib"; + +#if netcoreapp20 lib = Interop.Libdl.dlopen(libraryName, Interop.Libdl.RTLD_LAZY); +#else // use managed NativeLibrary API from .NET Core 3 onwards + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + NativeLibrary.TryLoad(libraryName, assembly, default, out lib); +#endif } else { @@ -41,13 +47,23 @@ private static IntPtr LoadNativeLibrary() // a global configuration setting. We prefer the "unversioned" shared object name, and fallback to // the name suffixed with ".0". libraryName = "libgdiplus.so"; + +#if netcoreapp20 lib = Interop.Libdl.dlopen(libraryName, Interop.Libdl.RTLD_LAZY); if (lib == IntPtr.Zero) { lib = Interop.Libdl.dlopen("libgdiplus.so.0", Interop.Libdl.RTLD_LAZY); } +#else // use managed NativeLibrary API from .NET Core 3 onwards + var assembly = System.Reflection.Assembly.GetExecutingAssembly(); + if (!NativeLibrary.TryLoad(libraryName, assembly, default, out lib)) + { + NativeLibrary.TryLoad("libgdiplus.so.0", assembly, default, out lib); + } +#endif } +#if netcoreapp20 // If we couldn't find libgdiplus in the system search path, try to look for libgdiplus in the // NuGet package folders. This matches the DllImport behavior. if (lib == IntPtr.Zero) @@ -59,21 +75,19 @@ private static IntPtr LoadNativeLibrary() var searchPath = Path.Combine(searchDirectory, libraryName); lib = Interop.Libdl.dlopen(searchPath, Interop.Libdl.RTLD_LAZY); - if (lib != IntPtr.Zero) { break; } } } +#endif // This function may return a null handle. If it does, individual functions loaded from it will throw a DllNotFoundException, // but not until an attempt is made to actually use the function (rather than load it). This matches how PInvokes behave. return lib; } - private static IntPtr LoadFunctionPointer(IntPtr nativeLibraryHandle, string functionName) => Interop.Libdl.dlsym(nativeLibraryHandle, functionName); - private static void PlatformInitialize() { LoadFunctionPointers(); diff --git a/src/System.Drawing.Common/src/System/Drawing/NativeStructs.Unix.cs b/src/System.Drawing.Common/src/System/Drawing/NativeStructs.Unix.cs index c9ad9dba0b09..ca82c84f83ba 100644 --- a/src/System.Drawing.Common/src/System/Drawing/NativeStructs.Unix.cs +++ b/src/System.Drawing.Common/src/System/Drawing/NativeStructs.Unix.cs @@ -34,7 +34,7 @@ namespace System.Drawing { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct LOGFONT { internal int lfHeight; diff --git a/src/System.Drawing.Common/src/System/Drawing/Printing/LibcupsNative.cs b/src/System.Drawing.Common/src/System/Drawing/Printing/LibcupsNative.cs index cff2025fb49c..6f0899f89f56 100644 --- a/src/System.Drawing.Common/src/System/Drawing/Printing/LibcupsNative.cs +++ b/src/System.Drawing.Common/src/System/Drawing/Printing/LibcupsNative.cs @@ -15,12 +15,18 @@ internal static class LibcupsNative private static IntPtr LoadLibcups() { // We allow both "libcups.so" and "libcups.so.2" to be loaded. +#if netcoreapp20 IntPtr lib = Interop.Libdl.dlopen("libcups.so", Interop.Libdl.RTLD_LAZY); if (lib == IntPtr.Zero) { lib = Interop.Libdl.dlopen("libcups.so.2", Interop.Libdl.RTLD_LAZY); } - +#else // use managed NativeLibrary API from .NET Core 3 onwards + if (!NativeLibrary.TryLoad("libcups.so", out IntPtr lib)) + { + NativeLibrary.TryLoad("libcups.so.2", out lib); + } +#endif return lib; } diff --git a/src/System.Drawing.Common/tests/FontTests.cs b/src/System.Drawing.Common/tests/FontTests.cs index 82387f167e3b..991ba0ed6d9b 100644 --- a/src/System.Drawing.Common/tests/FontTests.cs +++ b/src/System.Drawing.Common/tests/FontTests.cs @@ -834,7 +834,7 @@ public void ToLogFont_DisposedGraphics_ThrowsArgumentException() } } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public class LOGFONT { public int lfHeight; diff --git a/src/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj b/src/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj index a420ded97e1c..39906b81c23d 100644 --- a/src/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj +++ b/src/System.Drawing.Common/tests/System.Drawing.Common.Tests.csproj @@ -2,7 +2,7 @@ {4B93E684-0630-45F4-8F63-6C7788C9892F} true - 1.0.7 + 1.0.9 true netcoreapp-Debug;netcoreapp-Release;netfx-Debug;netfx-Release diff --git a/src/System.Globalization.Calendars/src/Configurations.props b/src/System.Globalization.Calendars/src/Configurations.props index 24f90bfd1544..5f6eee24b03d 100644 --- a/src/System.Globalization.Calendars/src/Configurations.props +++ b/src/System.Globalization.Calendars/src/Configurations.props @@ -2,9 +2,8 @@ uap-Windows_NT; + uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Globalization.Calendars/src/System.Globalization.Calendars.csproj b/src/System.Globalization.Calendars/src/System.Globalization.Calendars.csproj index ec117f45fcec..639be35ead58 100644 --- a/src/System.Globalization.Calendars/src/System.Globalization.Calendars.csproj +++ b/src/System.Globalization.Calendars/src/System.Globalization.Calendars.csproj @@ -3,7 +3,7 @@ System.Globalization.Calendars true {7126BEDB-0903-454A-8A2B-8BE1CBDF8F2E} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release diff --git a/src/System.Globalization/src/Configurations.props b/src/System.Globalization/src/Configurations.props index 8e16bc6010c4..a652fc326205 100644 --- a/src/System.Globalization/src/Configurations.props +++ b/src/System.Globalization/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Globalization/src/System.Globalization.csproj b/src/System.Globalization/src/System.Globalization.csproj index bf8c567dd4b3..bd6bc4b653ba 100644 --- a/src/System.Globalization/src/System.Globalization.csproj +++ b/src/System.Globalization/src/System.Globalization.csproj @@ -3,7 +3,7 @@ System.Globalization true {2395E8CA-73CB-40DF-BE40-A60BC189B737} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Decoder.cs b/src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Decoder.cs deleted file mode 100644 index 9eeb467e097b..000000000000 --- a/src/System.IO.Compression.Brotli/src/Interop/Interop.Brotli.Decoder.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Runtime.InteropServices; -using Microsoft.Win32.SafeHandles; -using size_t = System.IntPtr; - -internal static partial class Interop -{ - internal static partial class Brotli - { - [DllImport(Libraries.CompressionNative)] - internal static extern SafeBrotliDecoderHandle BrotliDecoderCreateInstance(IntPtr allocFunc, IntPtr freeFunc, IntPtr opaque); - - [DllImport(Libraries.CompressionNative)] - internal static extern unsafe int BrotliDecoderDecompressStream( - SafeBrotliDecoderHandle state, ref size_t availableIn, byte** nextIn, - ref size_t availableOut, byte** nextOut, out size_t totalOut); - - [DllImport(Libraries.CompressionNative)] - internal static extern unsafe bool BrotliDecoderDecompress(size_t availableInput, byte* inBytes, ref size_t availableOutput, byte* outBytes); - - [DllImport(Libraries.CompressionNative)] - internal static extern void BrotliDecoderDestroyInstance(IntPtr state); - - [DllImport(Libraries.CompressionNative)] - internal static extern bool BrotliDecoderIsFinished(SafeBrotliDecoderHandle state); - } -} - diff --git a/src/System.IO.Compression.Brotli/src/System.IO.Compression.Brotli.csproj b/src/System.IO.Compression.Brotli/src/System.IO.Compression.Brotli.csproj index 4fd831d19007..5416c29dc478 100644 --- a/src/System.IO.Compression.Brotli/src/System.IO.Compression.Brotli.csproj +++ b/src/System.IO.Compression.Brotli/src/System.IO.Compression.Brotli.csproj @@ -9,8 +9,7 @@ - - + diff --git a/src/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj b/src/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj index a63b6547a19f..5b8b48c38250 100644 --- a/src/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj +++ b/src/System.IO.Compression.Brotli/tests/System.IO.Compression.Brotli.Tests.csproj @@ -28,7 +28,7 @@ - + %(RecursiveDir)%(Filename)%(Extension) diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs index 622b0f982da6..7e6afd6b23ab 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs @@ -42,7 +42,7 @@ public static partial class ZipFile /// The path to the archive on the file system that is to be extracted. /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) => - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: false); + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwriteFiles: false); /// /// Extracts all of the files in the specified archive to a directory on the file system. @@ -74,9 +74,9 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti /// /// The path to the archive on the file system that is to be extracted. /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - /// True to indicate overwrite. - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwrite) => - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: overwrite); + /// True to indicate overwrite. + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwriteFiles) => + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwriteFiles: overwriteFiles); /// /// Extracts all of the files in the specified archive to a directory on the file system. @@ -131,7 +131,7 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti /// otherwise an is thrown. /// public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) => - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: entryNameEncoding, overwrite: false); + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: entryNameEncoding, overwriteFiles: false); /// /// Extracts all of the files in the specified archive to a directory on the file system. @@ -163,7 +163,7 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti /// /// The path to the archive on the file system that is to be extracted. /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - /// True to indicate overwrite. + /// True to indicate overwrite. /// The encoding to use when reading or writing entry names in this ZipArchive. /// /// NOTE: Specifying this parameter to values other than null is discouraged. /// However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support @@ -186,14 +186,14 @@ public static void ExtractToDirectory(string sourceArchiveFileName, string desti /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, /// otherwise an is thrown. /// - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding, bool overwrite) + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding, bool overwriteFiles) { if (sourceArchiveFileName == null) throw new ArgumentNullException(nameof(sourceArchiveFileName)); using (ZipArchive archive = Open(sourceArchiveFileName, ZipArchiveMode.Read, entryNameEncoding)) { - archive.ExtractToDirectory(destinationDirectoryName, overwrite); + archive.ExtractToDirectory(destinationDirectoryName, overwriteFiles); } } } diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs index c7d16a265d7d..f676f2423321 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs @@ -36,7 +36,7 @@ public static partial class ZipFileExtensions /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. /// Relative path information is interpreted as relative to the current working directory. public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) => - ExtractToDirectory(source, destinationDirectoryName, overwrite: false); + ExtractToDirectory(source, destinationDirectoryName, overwriteFiles: false); /// /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. @@ -65,8 +65,8 @@ public static void ExtractToDirectory(this ZipArchive source, string destination /// The path to the directory on the file system. /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. /// Relative path information is interpreted as relative to the current working directory. - /// True to indicate overwrite. - public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwrite) + /// True to indicate overwrite. + public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles) { if (source == null) throw new ArgumentNullException(nameof(source)); @@ -76,7 +76,7 @@ public static void ExtractToDirectory(this ZipArchive source, string destination foreach (ZipArchiveEntry entry in source.Entries) { - entry.ExtractRelativeToDirectory(destinationDirectoryName, overwrite); + entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles); } } } diff --git a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index 5e52414dad0f..eff9402799c0 100644 --- a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -38,7 +38,7 @@ - + %(RecursiveDir)%(Filename)%(Extension) diff --git a/src/System.IO.Compression/tests/System.IO.Compression.Tests.csproj b/src/System.IO.Compression/tests/System.IO.Compression.Tests.csproj index cb0370cf87d0..3f15eddec457 100644 --- a/src/System.IO.Compression/tests/System.IO.Compression.Tests.csproj +++ b/src/System.IO.Compression/tests/System.IO.Compression.Tests.csproj @@ -49,7 +49,7 @@ - + %(RecursiveDir)%(Filename)%(Extension) diff --git a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj index 94156a85060f..539e47ff4565 100644 --- a/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj +++ b/src/System.IO.FileSystem/src/System.IO.FileSystem.csproj @@ -11,6 +11,10 @@ $(NoWarn);414 + + + + @@ -187,6 +191,9 @@ Common\Interop\Windows\Interop.UNICODE_STRING.cs + + Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + Common\Interop\Windows\Interop.BOOLEAN.cs diff --git a/src/System.IO.Packaging/tests/System.IO.Packaging.Tests.csproj b/src/System.IO.Packaging/tests/System.IO.Packaging.Tests.csproj index 3a3ae7baff22..0123dd78642f 100644 --- a/src/System.IO.Packaging/tests/System.IO.Packaging.Tests.csproj +++ b/src/System.IO.Packaging/tests/System.IO.Packaging.Tests.csproj @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/src/System.IO.Pipelines/src/Resources/Strings.resx b/src/System.IO.Pipelines/src/Resources/Strings.resx index 8dc450466b2b..1801eb4fbe37 100644 --- a/src/System.IO.Pipelines/src/Resources/Strings.resx +++ b/src/System.IO.Pipelines/src/Resources/Strings.resx @@ -120,12 +120,6 @@ The PipeReader has already advanced past the provided position. - - Can't complete reader while reading. - - - Can't complete writer while writing. - Concurrent reads or writes are not supported. diff --git a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs index 3678ad63f03b..69925aa971c3 100644 --- a/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs +++ b/src/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs @@ -934,16 +934,19 @@ internal ValueTask WriteAsync(ReadOnlyMemory source, Cancella // state as writing AllocateWriteHeadIfNeeded(0); - if (source.Length <= _writingHeadMemory.Length) + lock (_sync) { - source.CopyTo(_writingHeadMemory); + if (source.Length <= _writingHeadMemory.Length) + { + source.CopyTo(_writingHeadMemory); - AdvanceCore(source.Length); - } - else - { - // This is the multi segment copy - WriteMultiSegment(source.Span); + AdvanceCore(source.Length); + } + else + { + // This is the multi segment copy + WriteMultiSegment(source.Span); + } } return FlushAsync(cancellationToken); diff --git a/src/System.IO.Pipelines/tests/Infrastructure/ThrowAfterNWritesStream.cs b/src/System.IO.Pipelines/tests/Infrastructure/ThrowAfterNWritesStream.cs index 260306a2a373..09ffe1018048 100644 --- a/src/System.IO.Pipelines/tests/Infrastructure/ThrowAfterNWritesStream.cs +++ b/src/System.IO.Pipelines/tests/Infrastructure/ThrowAfterNWritesStream.cs @@ -12,6 +12,8 @@ public class ThrowAfterNWritesStream : WriteOnlyStream private readonly int _maxWrites; private int _writes; + public int Writes => _writes; + public ThrowAfterNWritesStream(int maxWrites) { _maxWrites = maxWrites; diff --git a/src/System.IO.Pipelines/tests/PipeReaderCopyToAsyncTests.cs b/src/System.IO.Pipelines/tests/PipeReaderCopyToAsyncTests.cs index 07b7a60ea5dc..a0284d1771f9 100644 --- a/src/System.IO.Pipelines/tests/PipeReaderCopyToAsyncTests.cs +++ b/src/System.IO.Pipelines/tests/PipeReaderCopyToAsyncTests.cs @@ -69,21 +69,39 @@ public async Task MultiSegmentWritesWorks() [Fact] public async Task MultiSegmentWritesUntilFailure() { - using (var pool = new TestMemoryPool()) + using (var pool = new DisposeTrackingBufferPool()) { - var pipe = new Pipe(s_testOptions); + var pipe = new Pipe(new PipeOptions(pool, readerScheduler: PipeScheduler.Inline, useSynchronizationContext: false)); pipe.Writer.WriteEmpty(4096); pipe.Writer.WriteEmpty(4096); pipe.Writer.WriteEmpty(4096); await pipe.Writer.FlushAsync(); pipe.Writer.Complete(); + Assert.Equal(3, pool.CurrentlyRentedBlocks); + var stream = new ThrowAfterNWritesStream(2); - await Assert.ThrowsAsync(() => pipe.Reader.CopyToAsync(stream)); + try + { + await pipe.Reader.CopyToAsync(stream); + Assert.True(false, $"CopyToAsync should have failed, wrote {stream.Writes} times."); + } + catch(InvalidOperationException) + { + + } + + Assert.Equal(2, stream.Writes); + + Assert.Equal(1, pool.CurrentlyRentedBlocks); + Assert.Equal(2, pool.DisposedBlocks); ReadResult result = await pipe.Reader.ReadAsync(); Assert.Equal(4096, result.Buffer.Length); pipe.Reader.Complete(); + + Assert.Equal(0, pool.CurrentlyRentedBlocks); + Assert.Equal(3, pool.DisposedBlocks); } } diff --git a/src/System.IO.Pipelines/tests/PipeWriterTests.cs b/src/System.IO.Pipelines/tests/PipeWriterTests.cs index 187d8de8d40a..0ae5110673bb 100644 --- a/src/System.IO.Pipelines/tests/PipeWriterTests.cs +++ b/src/System.IO.Pipelines/tests/PipeWriterTests.cs @@ -232,5 +232,33 @@ public async Task WritesUsingGetMemoryWorks() pipe.Reader.Complete(); } + + [Fact] + public async Task CompleteWithLargeWriteThrows() + { + var pipe = new Pipe(); + pipe.Reader.Complete(); + + var task = Task.Run(async () => + { + await Task.Delay(10); + pipe.Writer.Complete(); + }); + + try + { + for (int i = 0; i < 1000; i++) + { + var buffer = new byte[10000000]; + await pipe.Writer.WriteAsync(buffer); + } + } + catch (InvalidOperationException) + { + // Complete while writing + } + + await task; + } } } diff --git a/src/System.IO.Ports/src/System.IO.Ports.csproj b/src/System.IO.Ports/src/System.IO.Ports.csproj index 82ce946748cc..aaed009912fd 100644 --- a/src/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/System.IO.Ports/src/System.IO.Ports.csproj @@ -12,7 +12,6 @@ - @@ -136,6 +135,9 @@ Common\Interop\Windows\Mincore\Interop.OpenCommPort.cs + + Common\Interop\Windows\Kernel32\Interop.FileOperations.cs + Common\Interop\Windows\Mincore\Interop.GetCommPorts.cs @@ -148,6 +150,9 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs + + Common\Interop\Windows\Kernel32\Interop.FileOperations.cs + diff --git a/src/System.IO.Ports/src/System/IO/Ports/NativeMethods.cs b/src/System.IO.Ports/src/System/IO/Ports/NativeMethods.cs deleted file mode 100644 index 96d14ac11d90..000000000000 --- a/src/System.IO.Ports/src/System/IO/Ports/NativeMethods.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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. - -namespace System.IO.Ports -{ - // TODO: These should be put in Common\Interop in classes like GenericOperations - internal class NativeMethods - { - internal const int FILE_ATTRIBUTE_NORMAL = 0x00000080; - internal const int FILE_FLAG_OVERLAPPED = 0x40000000; - - // The following are unique to the SerialPort/SerialStream classes - internal const byte ONESTOPBIT = 0; - internal const byte ONE5STOPBITS = 1; - internal const byte TWOSTOPBITS = 2; - - internal const int DTR_CONTROL_DISABLE = 0x00; - internal const int DTR_CONTROL_ENABLE = 0x01; - internal const int DTR_CONTROL_HANDSHAKE = 0x02; - - internal const int RTS_CONTROL_DISABLE = 0x00; - internal const int RTS_CONTROL_ENABLE = 0x01; - internal const int RTS_CONTROL_HANDSHAKE = 0x02; - internal const int RTS_CONTROL_TOGGLE = 0x03; - - internal const int MS_CTS_ON = 0x10; - internal const int MS_DSR_ON = 0x20; - internal const int MS_RING_ON = 0x40; - internal const int MS_RLSD_ON = 0x80; - - internal const byte EOFCHAR = 26; - - // Since C# does not provide access to bitfields and the native DCB structure contains - // a very necessary one, these are the positional offsets (from the right) of areas - // of the 32-bit integer used in SerialStream's SetDcbFlag() and GetDcbFlag() methods. - internal const int FBINARY = 0; - internal const int FPARITY = 1; - internal const int FOUTXCTSFLOW = 2; - internal const int FOUTXDSRFLOW = 3; - internal const int FDTRCONTROL = 4; - internal const int FDSRSENSITIVITY = 6; - internal const int FTXCONTINUEONXOFF = 7; - internal const int FOUTX = 8; - internal const int FINX = 9; - internal const int FERRORCHAR = 10; - internal const int FNULL = 11; - internal const int FRTSCONTROL = 12; - internal const int FABORTONOERROR = 14; - internal const int FDUMMY2 = 15; - - internal const int PURGE_TXABORT = 0x0001; // Kill the pending/current writes to the comm port. - internal const int PURGE_RXABORT = 0x0002; // Kill the pending/current reads to the comm port. - internal const int PURGE_TXCLEAR = 0x0004; // Kill the transmit queue if there. - internal const int PURGE_RXCLEAR = 0x0008; // Kill the typeahead buffer if there. - - internal const byte DEFAULTXONCHAR = (byte)17; - internal const byte DEFAULTXOFFCHAR = (byte)19; - - internal const int SETRTS = 3; // Set RTS high - internal const int CLRRTS = 4; // Set RTS low - internal const int SETDTR = 5; // Set DTR high - internal const int CLRDTR = 6; - - internal const int EV_RXCHAR = 0x01; - internal const int EV_RXFLAG = 0x02; - internal const int EV_CTS = 0x08; - internal const int EV_DSR = 0x10; - internal const int EV_RLSD = 0x20; - internal const int EV_BREAK = 0x40; - internal const int EV_ERR = 0x80; - internal const int EV_RING = 0x100; - internal const int ALL_EVENTS = 0x1fb; // don't use EV_TXEMPTY - - internal const int CE_RXOVER = 0x01; - internal const int CE_OVERRUN = 0x02; - internal const int CE_PARITY = 0x04; - internal const int CE_FRAME = 0x08; - internal const int CE_BREAK = 0x10; - internal const int CE_TXFULL = 0x100; - - internal const int MAXDWORD = -1; // note this is 0xfffffff, or UInt32.MaxValue, here used as an int - - internal const int NOPARITY = 0; - internal const int ODDPARITY = 1; - internal const int EVENPARITY = 2; - internal const int MARKPARITY = 3; - internal const int SPACEPARITY = 4; - } -} diff --git a/src/System.IO.Ports/src/System/IO/Ports/Parity.cs b/src/System.IO.Ports/src/System/IO/Ports/Parity.cs index 00b5574c7e72..81ea983da589 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/Parity.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/Parity.cs @@ -6,11 +6,11 @@ namespace System.IO.Ports { public enum Parity { - None = NativeMethods.NOPARITY, - Odd = NativeMethods.ODDPARITY, - Even = NativeMethods.EVENPARITY, - Mark = NativeMethods.MARKPARITY, - Space = NativeMethods.SPACEPARITY + None = 0, + Odd = 1, + Even = 2, + Mark = 3, + Space = 4 }; } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialError.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialError.cs index 4c9d397a7aa9..bb74de2d2126 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialError.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialError.cs @@ -6,10 +6,10 @@ namespace System.IO.Ports { public enum SerialError { - TXFull = NativeMethods.CE_TXFULL, - RXOver = NativeMethods.CE_RXOVER, - Overrun = NativeMethods.CE_OVERRUN, - RXParity = NativeMethods.CE_PARITY, - Frame = NativeMethods.CE_FRAME, + TXFull = 0x100, + RXOver = 0x01, + Overrun = 0x02, + RXParity = 0x04, + Frame = 0x08, } } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialPinChange.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialPinChange.cs index db469d4501c1..c6c3e0d4d800 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialPinChange.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialPinChange.cs @@ -6,10 +6,10 @@ namespace System.IO.Ports { public enum SerialPinChange { - CtsChanged = NativeMethods.EV_CTS, - DsrChanged = NativeMethods.EV_DSR, - CDChanged = NativeMethods.EV_RLSD, - Ring = NativeMethods.EV_RING, - Break = NativeMethods.EV_BREAK + CtsChanged = 0x08, + DsrChanged = 0x10, + CDChanged = 0x20, + Ring = 0x100, + Break = 0x40 } } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Uap.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Uap.cs index 3897ff2089a7..cb0dc59e3b65 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Uap.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Uap.cs @@ -13,7 +13,7 @@ public SafeFileHandle OpenPort(uint portNumber) return Interop.mincore.OpenCommPort( portNumber, Interop.Kernel32.GenericOperations.GENERIC_READ | Interop.Kernel32.GenericOperations.GENERIC_WRITE, - NativeMethods.FILE_FLAG_OVERLAPPED); + Interop.Kernel32.FileOperations.FILE_FLAG_OVERLAPPED); } } } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Win32.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Win32.cs index 52a4cb8fbfaf..05f77cd8eb1b 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Win32.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Win32.cs @@ -16,7 +16,7 @@ public SafeFileHandle OpenPort(uint portNumber) Interop.Kernel32.GenericOperations.GENERIC_READ | Interop.Kernel32.GenericOperations.GENERIC_WRITE, 0, // comm devices must be opened w/exclusive-access FileMode.Open, // comm devices must use OPEN_EXISTING - NativeMethods.FILE_FLAG_OVERLAPPED); + Interop.Kernel32.FileOperations.FILE_FLAG_OVERLAPPED); } } } diff --git a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs index 4b22ced9fcac..507de7f9bad2 100644 --- a/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs +++ b/src/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs @@ -141,15 +141,15 @@ internal bool DiscardNull { set { - int fNullFlag = GetDcbFlag(NativeMethods.FNULL); + int fNullFlag = GetDcbFlag(Interop.Kernel32.DCBFlags.FNULL); if (value == true && fNullFlag == 0 || value == false && fNullFlag == 1) { int fNullOld = fNullFlag; - SetDcbFlag(NativeMethods.FNULL, value ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FNULL, value ? 1 : 0); if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { - SetDcbFlag(NativeMethods.FNULL, fNullOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FNULL, fNullOld); throw Win32Marshal.GetExceptionForLastWin32Error(); } } @@ -160,24 +160,24 @@ internal bool DtrEnable { get { - int fDtrControl = GetDcbFlag(NativeMethods.FDTRCONTROL); + int fDtrControl = GetDcbFlag(Interop.Kernel32.DCBFlags.FDTRCONTROL); - return (fDtrControl == NativeMethods.DTR_CONTROL_ENABLE); + return (fDtrControl == Interop.Kernel32.DCBDTRFlowControl.DTR_CONTROL_ENABLE); } set { // first set the FDTRCONTROL field in the DCB struct - int fDtrControlOld = GetDcbFlag(NativeMethods.FDTRCONTROL); + int fDtrControlOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FDTRCONTROL); - SetDcbFlag(NativeMethods.FDTRCONTROL, value ? NativeMethods.DTR_CONTROL_ENABLE : NativeMethods.DTR_CONTROL_DISABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FDTRCONTROL, value ? Interop.Kernel32.DCBDTRFlowControl.DTR_CONTROL_ENABLE : Interop.Kernel32.DCBDTRFlowControl.DTR_CONTROL_DISABLE); if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { - SetDcbFlag(NativeMethods.FDTRCONTROL, fDtrControlOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FDTRCONTROL, fDtrControlOld); throw Win32Marshal.GetExceptionForLastWin32Error(); } // then set the actual pin - if (!Interop.Kernel32.EscapeCommFunction(_handle, value ? NativeMethods.SETDTR : NativeMethods.CLRDTR)) + if (!Interop.Kernel32.EscapeCommFunction(_handle, value ? Interop.Kernel32.CommFunctions.SETDTR : Interop.Kernel32.CommFunctions.CLRDTR)) throw Win32Marshal.GetExceptionForLastWin32Error(); } } @@ -194,39 +194,39 @@ internal Handshake Handshake // in the DCB, handshake affects the fRtsControl, fOutxCtsFlow, and fInX, fOutX fields, // so we must save everything in that closure before making any changes. Handshake handshakeOld = _handshake; - int fInOutXOld = GetDcbFlag(NativeMethods.FINX); - int fOutxCtsFlowOld = GetDcbFlag(NativeMethods.FOUTXCTSFLOW); - int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL); + int fInOutXOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FINX); + int fOutxCtsFlowOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXCTSFLOW); + int fRtsControlOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL); _handshake = value; int fInXOutXFlag = (_handshake == Handshake.XOnXOff || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0; - SetDcbFlag(NativeMethods.FINX, fInXOutXFlag); - SetDcbFlag(NativeMethods.FOUTX, fInXOutXFlag); + SetDcbFlag(Interop.Kernel32.DCBFlags.FINX, fInXOutXFlag); + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTX, fInXOutXFlag); - SetDcbFlag(NativeMethods.FOUTXCTSFLOW, (_handshake == Handshake.RequestToSend || + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXCTSFLOW, (_handshake == Handshake.RequestToSend || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0); if ((_handshake == Handshake.RequestToSend || _handshake == Handshake.RequestToSendXOnXOff)) { - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_HANDSHAKE); } else if (_rtsEnable) { - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_ENABLE); } else { - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_DISABLE); } if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { _handshake = handshakeOld; - SetDcbFlag(NativeMethods.FINX, fInOutXOld); - SetDcbFlag(NativeMethods.FOUTX, fInOutXOld); - SetDcbFlag(NativeMethods.FOUTXCTSFLOW, fOutxCtsFlowOld); - SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FINX, fInOutXOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTX, fInOutXOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXCTSFLOW, fOutxCtsFlowOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, fRtsControlOld); throw Win32Marshal.GetExceptionForLastWin32Error(); } @@ -249,30 +249,30 @@ internal Parity Parity // in the DCB structure, the parity setting also potentially effects: // fParity, fErrorChar, ErrorChar // so these must be saved as well. - int fParityOld = GetDcbFlag(NativeMethods.FPARITY); + int fParityOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FPARITY); byte ErrorCharOld = _dcb.ErrorChar; - int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR); + int fErrorCharOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR); _dcb.Parity = (byte)value; int parityFlag = (_dcb.Parity == (byte)Parity.None) ? 0 : 1; - SetDcbFlag(NativeMethods.FPARITY, parityFlag); + SetDcbFlag(Interop.Kernel32.DCBFlags.FPARITY, parityFlag); if (parityFlag == 1) { - SetDcbFlag(NativeMethods.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); _dcb.ErrorChar = _parityReplace; } else { - SetDcbFlag(NativeMethods.FERRORCHAR, 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, 0); _dcb.ErrorChar = (byte)'\0'; } if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { _dcb.Parity = parityOld; - SetDcbFlag(NativeMethods.FPARITY, fParityOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FPARITY, fParityOld); _dcb.ErrorChar = ErrorCharOld; - SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, fErrorCharOld); throw Win32Marshal.GetExceptionForLastWin32Error(); } @@ -291,24 +291,24 @@ internal byte ParityReplace { byte parityReplaceOld = _parityReplace; byte errorCharOld = _dcb.ErrorChar; - int fErrorCharOld = GetDcbFlag(NativeMethods.FERRORCHAR); + int fErrorCharOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR); _parityReplace = value; - if (GetDcbFlag(NativeMethods.FPARITY) == 1) + if (GetDcbFlag(Interop.Kernel32.DCBFlags.FPARITY) == 1) { - SetDcbFlag(NativeMethods.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); _dcb.ErrorChar = _parityReplace; } else { - SetDcbFlag(NativeMethods.FERRORCHAR, 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, 0); _dcb.ErrorChar = (byte)'\0'; } if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { _parityReplace = parityReplaceOld; - SetDcbFlag(NativeMethods.FERRORCHAR, fErrorCharOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, fErrorCharOld); _dcb.ErrorChar = errorCharOld; throw Win32Marshal.GetExceptionForLastWin32Error(); } @@ -354,21 +354,21 @@ public override int ReadTimeout { _commTimeouts.ReadTotalTimeoutConstant = 0; _commTimeouts.ReadTotalTimeoutMultiplier = 0; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } else if (value == SerialPort.InfiniteTimeout) { // SetCommTimeouts doesn't like a value of -1 for some reason, so // we'll use -2(infiniteTimeoutConst) to represent infinite. _commTimeouts.ReadTotalTimeoutConstant = infiniteTimeoutConst; - _commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadTotalTimeoutMultiplier = Interop.Kernel32.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } else { _commTimeouts.ReadTotalTimeoutConstant = value; - _commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadTotalTimeoutMultiplier = Interop.Kernel32.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } if (Interop.Kernel32.SetCommTimeouts(_handle, ref _commTimeouts) == false) @@ -385,11 +385,11 @@ internal bool RtsEnable { get { - int fRtsControl = GetDcbFlag(NativeMethods.FRTSCONTROL); - if (fRtsControl == NativeMethods.RTS_CONTROL_HANDSHAKE) + int fRtsControl = GetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL); + if (fRtsControl == Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_HANDSHAKE) throw new InvalidOperationException(SR.CantSetRtsWithHandshaking); - return (fRtsControl == NativeMethods.RTS_CONTROL_ENABLE); + return (fRtsControl == Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_ENABLE); } set { @@ -398,23 +398,23 @@ internal bool RtsEnable if (value != _rtsEnable) { - int fRtsControlOld = GetDcbFlag(NativeMethods.FRTSCONTROL); + int fRtsControlOld = GetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL); _rtsEnable = value; if (value) - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_ENABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_ENABLE); else - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_DISABLE); if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) { - SetDcbFlag(NativeMethods.FRTSCONTROL, fRtsControlOld); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, fRtsControlOld); // set it back to the old value on a failure _rtsEnable = !_rtsEnable; throw Win32Marshal.GetExceptionForLastWin32Error(); } - if (!Interop.Kernel32.EscapeCommFunction(_handle, value ? NativeMethods.SETRTS : NativeMethods.CLRRTS)) + if (!Interop.Kernel32.EscapeCommFunction(_handle, value ? Interop.Kernel32.CommFunctions.SETRTS : Interop.Kernel32.CommFunctions.CLRRTS)) throw Win32Marshal.GetExceptionForLastWin32Error(); } } @@ -428,9 +428,18 @@ internal StopBits StopBits Debug.Assert(!(value < StopBits.One || value > StopBits.OnePointFive), "An invalid value was passed to StopBits"); byte nativeValue = 0; - if (value == StopBits.One) nativeValue = NativeMethods.ONESTOPBIT; - else if (value == StopBits.OnePointFive) nativeValue = NativeMethods.ONE5STOPBITS; - else nativeValue = NativeMethods.TWOSTOPBITS; + if (value == StopBits.One) + { + nativeValue = Interop.Kernel32.DCBStopBits.ONESTOPBIT; + } + else if (value == StopBits.OnePointFive) + { + nativeValue = Interop.Kernel32.DCBStopBits.ONE5STOPBITS; + } + else + { + nativeValue = Interop.Kernel32.DCBStopBits.TWOSTOPBITS; + } if (nativeValue != _dcb.StopBits) { @@ -483,7 +492,7 @@ internal bool CDHolding if (Interop.Kernel32.GetCommModemStatus(_handle, ref pinStatus) == false) throw Win32Marshal.GetExceptionForLastWin32Error(); - return (NativeMethods.MS_RLSD_ON & pinStatus) != 0; + return (Interop.Kernel32.CommModemState.MS_RLSD_ON & pinStatus) != 0; } } @@ -494,7 +503,7 @@ internal bool CtsHolding int pinStatus = 0; if (Interop.Kernel32.GetCommModemStatus(_handle, ref pinStatus) == false) throw Win32Marshal.GetExceptionForLastWin32Error(); - return (NativeMethods.MS_CTS_ON & pinStatus) != 0; + return (Interop.Kernel32.CommModemState.MS_CTS_ON & pinStatus) != 0; } } @@ -507,7 +516,7 @@ internal bool DsrHolding if (Interop.Kernel32.GetCommModemStatus(_handle, ref pinStatus) == false) throw Win32Marshal.GetExceptionForLastWin32Error(); - return (NativeMethods.MS_DSR_ON & pinStatus) != 0; + return (Interop.Kernel32.CommModemState.MS_DSR_ON & pinStatus) != 0; } } @@ -622,7 +631,7 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits // query and cache the initial RtsEnable value // so that set_RtsEnable can do the (value != rtsEnable) optimization - _rtsEnable = (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_ENABLE); + _rtsEnable = (GetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL) == Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_ENABLE); // now set this.RtsEnable to the specified value. // Handshake takes precedence, this will be a nop if @@ -635,21 +644,21 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits { _commTimeouts.ReadTotalTimeoutConstant = 0; _commTimeouts.ReadTotalTimeoutMultiplier = 0; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } else if (readTimeout == SerialPort.InfiniteTimeout) { // SetCommTimeouts doesn't like a value of -1 for some reason, so // we'll use -2(infiniteTimeoutConst) to represent infinite. _commTimeouts.ReadTotalTimeoutConstant = infiniteTimeoutConst; - _commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadTotalTimeoutMultiplier = Interop.Kernel32.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } else { _commTimeouts.ReadTotalTimeoutConstant = readTimeout; - _commTimeouts.ReadTotalTimeoutMultiplier = NativeMethods.MAXDWORD; - _commTimeouts.ReadIntervalTimeout = NativeMethods.MAXDWORD; + _commTimeouts.ReadTotalTimeoutMultiplier = Interop.Kernel32.MAXDWORD; + _commTimeouts.ReadIntervalTimeout = Interop.Kernel32.MAXDWORD; } _commTimeouts.WriteTotalTimeoutMultiplier = 0; @@ -667,7 +676,7 @@ internal SerialStream(string portName, int baudRate, Parity parity, int dataBits } // monitor all events except TXEMPTY - Interop.Kernel32.SetCommMask(_handle, NativeMethods.ALL_EVENTS); + Interop.Kernel32.SetCommMask(_handle, Interop.Kernel32.CommEvents.ALL_EVENTS); // prep. for starting event cycle. _eventRunner = new EventLoopRunner(this); @@ -701,7 +710,7 @@ protected override void Dispose(bool disposing) // turn off all events and signal WaitCommEvent Interop.Kernel32.SetCommMask(_handle, 0); - if (!Interop.Kernel32.EscapeCommFunction(_handle, NativeMethods.CLRDTR)) + if (!Interop.Kernel32.EscapeCommFunction(_handle, Interop.Kernel32.CommFunctions.CLRDTR)) { int hr = Marshal.GetLastWin32Error(); @@ -825,14 +834,14 @@ public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, internal void DiscardInBuffer() { - if (Interop.Kernel32.PurgeComm(_handle, NativeMethods.PURGE_RXCLEAR | NativeMethods.PURGE_RXABORT) == false) + if (Interop.Kernel32.PurgeComm(_handle, Interop.Kernel32.PurgeFlags.PURGE_RXCLEAR | Interop.Kernel32.PurgeFlags.PURGE_RXABORT) == false) throw Win32Marshal.GetExceptionForLastWin32Error(); } // Uses Win32 method to dump out the xmit buffer; analagous to MSComm's "OutBufferCount = 0" internal void DiscardOutBuffer() { - if (Interop.Kernel32.PurgeComm(_handle, NativeMethods.PURGE_TXCLEAR | NativeMethods.PURGE_TXABORT) == false) + if (Interop.Kernel32.PurgeComm(_handle, Interop.Kernel32.PurgeFlags.PURGE_TXCLEAR | Interop.Kernel32.PurgeFlags.PURGE_TXABORT) == false) throw Win32Marshal.GetExceptionForLastWin32Error(); } @@ -1156,13 +1165,13 @@ private unsafe void InitializeDCB(int baudRate, Parity parity, int dataBits, Sto switch (stopBits) { case StopBits.One: - _dcb.StopBits = NativeMethods.ONESTOPBIT; + _dcb.StopBits = Interop.Kernel32.DCBStopBits.ONESTOPBIT; break; case StopBits.OnePointFive: - _dcb.StopBits = NativeMethods.ONE5STOPBITS; + _dcb.StopBits = Interop.Kernel32.DCBStopBits.ONE5STOPBITS; break; case StopBits.Two: - _dcb.StopBits = NativeMethods.TWOSTOPBITS; + _dcb.StopBits = Interop.Kernel32.DCBStopBits.TWOSTOPBITS; break; default: Debug.Fail("Invalid value for stopBits"); @@ -1173,40 +1182,40 @@ private unsafe void InitializeDCB(int baudRate, Parity parity, int dataBits, Sto // SetDcbFlag, GetDcbFlag expose access to each of the relevant bits of the 32-bit integer // storing all flags of the DCB. C# provides no direct means of manipulating bit fields, so // this is the solution. - SetDcbFlag(NativeMethods.FPARITY, ((parity == Parity.None) ? 0 : 1)); + SetDcbFlag(Interop.Kernel32.DCBFlags.FPARITY, ((parity == Parity.None) ? 0 : 1)); - SetDcbFlag(NativeMethods.FBINARY, 1); // always true for communications resources + SetDcbFlag(Interop.Kernel32.DCBFlags.FBINARY, 1); // always true for communications resources // set DCB fields implied by default and the arguments given. // Boolean fields in C# must become 1, 0 to properly set the bit flags in the unmanaged DCB struct - SetDcbFlag(NativeMethods.FOUTXCTSFLOW, ((_handshake == Handshake.RequestToSend || + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXCTSFLOW, ((_handshake == Handshake.RequestToSend || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0)); - // SetDcbFlag(NativeMethods.FOUTXDSRFLOW, (dsrTimeout != 0L) ? 1 : 0); - SetDcbFlag(NativeMethods.FOUTXDSRFLOW, 0); // dsrTimeout is always set to 0. - SetDcbFlag(NativeMethods.FDTRCONTROL, NativeMethods.DTR_CONTROL_DISABLE); - SetDcbFlag(NativeMethods.FDSRSENSITIVITY, 0); // this should remain off - SetDcbFlag(NativeMethods.FINX, (_handshake == Handshake.XOnXOff || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0); - SetDcbFlag(NativeMethods.FOUTX, (_handshake == Handshake.XOnXOff || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0); + // SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXDSRFLOW, (dsrTimeout != 0L) ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTXDSRFLOW, 0); // dsrTimeout is always set to 0. + SetDcbFlag(Interop.Kernel32.DCBFlags.FDTRCONTROL, Interop.Kernel32.DCBDTRFlowControl.DTR_CONTROL_DISABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FDSRSENSITIVITY, 0); // this should remain off + SetDcbFlag(Interop.Kernel32.DCBFlags.FINX, (_handshake == Handshake.XOnXOff || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FOUTX, (_handshake == Handshake.XOnXOff || _handshake == Handshake.RequestToSendXOnXOff) ? 1 : 0); // if no parity, we have no error character (i.e. ErrorChar = '\0' or null character) if (parity != Parity.None) { - SetDcbFlag(NativeMethods.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, (_parityReplace != '\0') ? 1 : 0); _dcb.ErrorChar = _parityReplace; } else { - SetDcbFlag(NativeMethods.FERRORCHAR, 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FERRORCHAR, 0); _dcb.ErrorChar = (byte)'\0'; } // this method only runs once in the constructor, so we only have the default value to use. // Later the user may change this via the NullDiscard property. - SetDcbFlag(NativeMethods.FNULL, discardNull ? 1 : 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FNULL, discardNull ? 1 : 0); // SerialStream does not handle the fAbortOnError behaviour, so we must make sure it's not enabled - SetDcbFlag(NativeMethods.FABORTONOERROR, 0); + SetDcbFlag(Interop.Kernel32.DCBFlags.FABORTONOERROR, 0); // Setting RTS control, which is RTS_CONTROL_HANDSHAKE if RTS / RTS-XOnXOff handshaking // used, RTS_ENABLE (RTS pin used during operation) if rtsEnable true but XOnXoff / No handshaking @@ -1214,25 +1223,25 @@ private unsafe void InitializeDCB(int baudRate, Parity parity, int dataBits, Sto if ((_handshake == Handshake.RequestToSend || _handshake == Handshake.RequestToSendXOnXOff)) { - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_HANDSHAKE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_HANDSHAKE); } - else if (GetDcbFlag(NativeMethods.FRTSCONTROL) == NativeMethods.RTS_CONTROL_HANDSHAKE) + else if (GetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL) == Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_HANDSHAKE) { - SetDcbFlag(NativeMethods.FRTSCONTROL, NativeMethods.RTS_CONTROL_DISABLE); + SetDcbFlag(Interop.Kernel32.DCBFlags.FRTSCONTROL, Interop.Kernel32.DCBRTSFlowControl.RTS_CONTROL_DISABLE); } - _dcb.XonChar = NativeMethods.DEFAULTXONCHAR; // may be exposed later but for now, constant - _dcb.XoffChar = NativeMethods.DEFAULTXOFFCHAR; + _dcb.XonChar = Interop.Kernel32.DCB.DEFAULTXONCHAR; // may be exposed later but for now, constant + _dcb.XoffChar = Interop.Kernel32.DCB.DEFAULTXOFFCHAR; // minimum number of bytes allowed in each buffer before flow control activated // heuristically, this has been set at 1/4 of the buffer size _dcb.XonLim = _dcb.XoffLim = (ushort)(_commProp.dwCurrentRxQueue / 4); - _dcb.EofChar = NativeMethods.EOFCHAR; + _dcb.EofChar = Interop.Kernel32.DCB.EOFCHAR; //OLD MSCOMM: dcb.EvtChar = (byte) 0; // now changed to make use of RXFlag WaitCommEvent event => Eof WaitForCommEvent event - _dcb.EvtChar = NativeMethods.EOFCHAR; + _dcb.EvtChar = Interop.Kernel32.DCB.EOFCHAR; // set DCB structure if (Interop.Kernel32.SetCommState(_handle, ref _dcb) == false) @@ -1243,18 +1252,17 @@ private unsafe void InitializeDCB(int baudRate, Parity parity, int dataBits, Sto // Here we provide a method for getting the flags of the Device Control Block structure dcb // associated with each instance of SerialStream, i.e. this method gets myStream.dcb.Flags - // Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc. internal int GetDcbFlag(int whichFlag) { uint mask; - Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "GetDcbFlag needs to fit into enum!"); + Debug.Assert(whichFlag >= Interop.Kernel32.DCBFlags.FBINARY && whichFlag <= Interop.Kernel32.DCBFlags.FDUMMY2, "GetDcbFlag needs to fit into enum!"); - if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL) + if (whichFlag == Interop.Kernel32.DCBFlags.FDTRCONTROL || whichFlag == Interop.Kernel32.DCBFlags.FRTSCONTROL) { mask = 0x3; } - else if (whichFlag == NativeMethods.FDUMMY2) + else if (whichFlag == Interop.Kernel32.DCBFlags.FDUMMY2) { mask = 0x1FFFF; } @@ -1269,19 +1277,18 @@ internal int GetDcbFlag(int whichFlag) // Since C# applications have to provide a workaround for accessing and setting bitfields in unmanaged code, // here we provide methods for getting and setting the Flags field of the Device Control Block structure dcb // associated with each instance of SerialStream, i.e. this method sets myStream.dcb.Flags - // Flags are any of the constants in NativeMethods such as FBINARY, FDTRCONTROL, etc. internal void SetDcbFlag(int whichFlag, int setting) { uint mask; setting = setting << whichFlag; - Debug.Assert(whichFlag >= NativeMethods.FBINARY && whichFlag <= NativeMethods.FDUMMY2, "SetDcbFlag needs to fit into enum!"); + Debug.Assert(whichFlag >= Interop.Kernel32.DCBFlags.FBINARY && whichFlag <= Interop.Kernel32.DCBFlags.FDUMMY2, "SetDcbFlag needs to fit into enum!"); - if (whichFlag == NativeMethods.FDTRCONTROL || whichFlag == NativeMethods.FRTSCONTROL) + if (whichFlag == Interop.Kernel32.DCBFlags.FDTRCONTROL || whichFlag == Interop.Kernel32.DCBFlags.FRTSCONTROL) { mask = 0x3; } - else if (whichFlag == NativeMethods.FDUMMY2) + else if (whichFlag == Interop.Kernel32.DCBFlags.FDUMMY2) { mask = 0x1FFFF; } @@ -1681,7 +1688,7 @@ private void CallEvents(int nativeEvents) // EV_ERR includes only CE_FRAME, CE_OVERRUN, and CE_RXPARITY // To catch errors such as CE_RXOVER, we need to call CleanCommErrors bit more regularly. // EV_RXCHAR is perhaps too loose an event to look for overflow errors but a safe side to err... - if ((nativeEvents & (NativeMethods.EV_ERR | NativeMethods.EV_RXCHAR)) != 0) + if ((nativeEvents & (Interop.Kernel32.CommEvents.EV_ERR | Interop.Kernel32.CommEvents.EV_RXCHAR)) != 0) { int errors = 0; if (Interop.Kernel32.ClearCommError(handle, ref errors, IntPtr.Zero) == false) diff --git a/src/System.IO.UnmanagedMemoryStream/src/Configurations.props b/src/System.IO.UnmanagedMemoryStream/src/Configurations.props index 7be6fd9cfa54..5f6eee24b03d 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/Configurations.props +++ b/src/System.IO.UnmanagedMemoryStream/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj b/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj index 114d8300ce19..0cbbd8b5eaba 100644 --- a/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj +++ b/src/System.IO.UnmanagedMemoryStream/src/System.IO.UnmanagedMemoryStream.csproj @@ -3,7 +3,7 @@ {BCF9255A-4321-4277-AD7D-F5094092C554} System.IO.UnmanagedMemoryStream true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.IO/tests/MemoryStream/MemoryStream.ConstructorTests.cs b/src/System.IO/tests/MemoryStream/MemoryStream.ConstructorTests.cs index ed4ce70112db..55abd1bff118 100644 --- a/src/System.IO/tests/MemoryStream/MemoryStream.ConstructorTests.cs +++ b/src/System.IO/tests/MemoryStream/MemoryStream.ConstructorTests.cs @@ -35,7 +35,10 @@ public static void MemoryStream_Ctor_InvalidCapacities() { Assert.Throws(() => new MemoryStream(int.MinValue)); Assert.Throws(() => new MemoryStream(-1)); - Assert.Throws(() => new MemoryStream(int.MaxValue)); + if (PlatformDetection.IsNotIntMaxValueArrayIndexSupported) + { + Assert.Throws(() => new MemoryStream(int.MaxValue)); + } } } } diff --git a/src/System.IO/tests/MemoryStream/MemoryStream.GetBufferTests.cs b/src/System.IO/tests/MemoryStream/MemoryStream.GetBufferTests.cs index 30e393ebe2f1..f9e67fddf7c7 100644 --- a/src/System.IO/tests/MemoryStream/MemoryStream.GetBufferTests.cs +++ b/src/System.IO/tests/MemoryStream/MemoryStream.GetBufferTests.cs @@ -31,6 +31,15 @@ public void MemoryStream_GetBuffer_Exposable() Assert.Equal(500, buffer.Length); } + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://github.com/dotnet/coreclr/pull/23700")] + [Fact] + public void MemoryStream_GetBuffer_AfterCapacityReset() + { + var ms = new MemoryStream(100); + ms.Capacity = 0; + Assert.NotNull(ms.GetBuffer()); + } + [Fact] public void MemoryStream_GetBuffer() { diff --git a/src/System.IO/tests/StreamWriter/StreamWriter.CloseTests.cs b/src/System.IO/tests/StreamWriter/StreamWriter.CloseTests.cs index 60e2b54e06e9..a1913fc63d40 100644 --- a/src/System.IO/tests/StreamWriter/StreamWriter.CloseTests.cs +++ b/src/System.IO/tests/StreamWriter/StreamWriter.CloseTests.cs @@ -41,7 +41,10 @@ public void AfterCloseThrows() private void ValidateDisposedExceptions(StreamWriter sw) { - Assert.Null(sw.BaseStream); + if (PlatformDetection.IsNetCore) + { + Assert.NotNull(sw.BaseStream); + } Assert.Throws(() => sw.Write('A')); Assert.Throws(() => sw.Write("hello")); Assert.Throws(() => sw.Flush()); diff --git a/src/System.Linq.Expressions/src/Configurations.props b/src/System.Linq.Expressions/src/Configurations.props index c7698697cf40..592103a98c2f 100644 --- a/src/System.Linq.Expressions/src/Configurations.props +++ b/src/System.Linq.Expressions/src/Configurations.props @@ -3,8 +3,6 @@ netcoreapp-Unix; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uapaot-Windows_NT; uap-Windows_NT; diff --git a/src/System.Linq.Expressions/src/System.Linq.Expressions.csproj b/src/System.Linq.Expressions/src/System.Linq.Expressions.csproj index 2616692e8e1d..888087360c12 100644 --- a/src/System.Linq.Expressions/src/System.Linq.Expressions.csproj +++ b/src/System.Linq.Expressions/src/System.Linq.Expressions.csproj @@ -2,7 +2,7 @@ true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release {AEF718E9-D4FC-418F-A7AE-ED6B2C7B3787} diff --git a/src/System.Linq.Expressions/tests/DebuggerTypeProxy/ExpressionDebuggerTypeProxyTests.cs b/src/System.Linq.Expressions/tests/DebuggerTypeProxy/ExpressionDebuggerTypeProxyTests.cs index 6fa359286834..153e9e12f53e 100644 --- a/src/System.Linq.Expressions/tests/DebuggerTypeProxy/ExpressionDebuggerTypeProxyTests.cs +++ b/src/System.Linq.Expressions/tests/DebuggerTypeProxy/ExpressionDebuggerTypeProxyTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Linq.Expressions.Tests @@ -32,7 +33,11 @@ private Type GetDebugViewType(Type type) { var att = (DebuggerTypeProxyAttribute) - type.GetCustomAttributes().Single(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + type.GetCustomAttributes().SingleOrDefault(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + if (att == null) + { + return null; + } string proxyName = att.ProxyTypeName; proxyName = proxyName.Substring(0, proxyName.IndexOf(',')); return type.GetTypeInfo().Assembly.GetType(proxyName); @@ -46,7 +51,7 @@ private void AssertIsReadOnly(ICollection collection) Assert.Throws(() => collection.Remove(default(T))); } - [Theory] + [ConditionalTheory] [MemberData(nameof(BinaryExpressionProxy))] [MemberData(nameof(BlockExpressionProxy))] [MemberData(nameof(CatchBlockProxy))] @@ -77,6 +82,10 @@ public void VerifyDebugView(object obj) { Type type = obj.GetType(); Type viewType = GetDebugViewType(type); + if (viewType == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {type}."); + } object view = viewType.GetConstructors().Single().Invoke(new[] {obj}); IEnumerable properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) @@ -155,11 +164,15 @@ public void VerifyDebugView(object obj) } } - [Theory, MemberData(nameof(OnePerType))] + [ConditionalTheory, MemberData(nameof(OnePerType))] public void ThrowOnNullToCtor(object sourceObject) { Type type = sourceObject.GetType(); Type viewType = GetDebugViewType(type); + if (viewType == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {type}."); + } ConstructorInfo ctor = viewType.GetConstructors().Single(); TargetInvocationException tie = Assert.Throws(() => ctor.Invoke(new object[] { null })); ArgumentNullException ane = (ArgumentNullException)tie.InnerException; diff --git a/src/System.Linq.Expressions/tests/Dynamic/BindingRestrictionsProxyTests.cs b/src/System.Linq.Expressions/tests/Dynamic/BindingRestrictionsProxyTests.cs index 7d83fb37aaad..267c0ef1ce9d 100644 --- a/src/System.Linq.Expressions/tests/Dynamic/BindingRestrictionsProxyTests.cs +++ b/src/System.Linq.Expressions/tests/Dynamic/BindingRestrictionsProxyTests.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Dynamic.Tests @@ -45,14 +46,19 @@ public BindingRestrictionsProxyProxy(object proxy) public override string ToString() => (string)ToStringMeth.Invoke(_proxy, new object[0]); } + private static readonly Type BindingRestrictionsDebugViewType = GetDebugViewType(typeof(BindingRestrictions)); private static readonly ConstructorInfo BindingRestrictionsProxyCtor = - GetDebugViewType(typeof(BindingRestrictions)).GetConstructors().Single(); + BindingRestrictionsDebugViewType?.GetConstructors().Single(); private static Type GetDebugViewType(Type type) { var att = (DebuggerTypeProxyAttribute) - type.GetCustomAttributes().Single(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + type.GetCustomAttributes().SingleOrDefault(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + if (att == null) + { + return null; + } string proxyName = att.ProxyTypeName; proxyName = proxyName.Substring(0, proxyName.IndexOf(',')); return type.GetTypeInfo().Assembly.GetType(proxyName); @@ -61,9 +67,13 @@ private static Type GetDebugViewType(Type type) private static BindingRestrictionsProxyProxy GetDebugViewObject(object obj) => new BindingRestrictionsProxyProxy(BindingRestrictionsProxyCtor.Invoke(new[] {obj})); - [Fact] + [ConditionalFact] public void EmptyRestiction() { + if (BindingRestrictionsDebugViewType == null) + { + throw new SkipTestException("Didn't find DebuggerTypeProxyAttribute on BindingRestrictions."); + } BindingRestrictions empty = BindingRestrictions.Empty; BindingRestrictionsProxyProxy view = GetDebugViewObject(empty); Assert.True(view.IsEmpty); @@ -74,9 +84,13 @@ public void EmptyRestiction() Assert.Equal(empty.ToExpression().ToString(), view.ToString()); } - [Fact] + [ConditionalFact] public void CustomRestriction() { + if (BindingRestrictionsDebugViewType == null) + { + throw new SkipTestException("Didn't find DebuggerTypeProxyAttribute on BindingRestrictions."); + } ConstantExpression exp = Expression.Constant(false); BindingRestrictions custom = BindingRestrictions.GetExpressionRestriction(exp); BindingRestrictionsProxyProxy view = GetDebugViewObject(custom); @@ -89,7 +103,7 @@ public void CustomRestriction() Assert.Equal(exp.ToString(), view.ToString()); } - [Fact] + [ConditionalFact] public void MergedRestrictionsProperties() { var exps = new Expression[] @@ -107,6 +121,11 @@ public void MergedRestrictionsProperties() br = br.Merge(res); } + if (BindingRestrictionsDebugViewType == null) + { + throw new SkipTestException("Didn't find DebuggerTypeProxyAttribute on BindingRestrictions."); + } + BindingRestrictionsProxyProxy view = GetDebugViewObject(br); Assert.False(view.IsEmpty); @@ -119,7 +138,7 @@ public void MergedRestrictionsProperties() Assert.True(viewedRestrictions.All(r => restrictions.Contains(r))); } - [Fact] + [ConditionalFact] public void MergedRestrictionsExpressions() { var exps = new Expression[] @@ -134,6 +153,11 @@ public void MergedRestrictionsExpressions() br = br.Merge(BindingRestrictions.GetExpressionRestriction(exp)); } + if (BindingRestrictionsDebugViewType == null) + { + throw new SkipTestException("Didn't find DebuggerTypeProxyAttribute on BindingRestrictions."); + } + BindingRestrictionsProxyProxy view = GetDebugViewObject(br); // The expression in the view will be a tree of AndAlso nodes. @@ -171,9 +195,13 @@ public void MergedRestrictionsExpressions() Assert.True(notAndAlso.All(ex => exps.Contains(ex))); } - [Fact] + [ConditionalFact] public void ThrowOnNullToCtor() { + if (BindingRestrictionsDebugViewType == null) + { + throw new SkipTestException("Didn't find DebuggerTypeProxyAttribute on BindingRestrictions."); + } TargetInvocationException tie = Assert.Throws(() => BindingRestrictionsProxyCtor.Invoke(new object[] {null})); ArgumentNullException ane = (ArgumentNullException)tie.InnerException; if (!PlatformDetection.IsNetNative) // The .NET Native toolchain optimizes away exception ParamNames diff --git a/src/System.Linq.Expressions/tests/Dynamic/ExpandoObjectProxyTests.cs b/src/System.Linq.Expressions/tests/Dynamic/ExpandoObjectProxyTests.cs index 8375ab412e82..7d759a1c75f3 100644 --- a/src/System.Linq.Expressions/tests/Dynamic/ExpandoObjectProxyTests.cs +++ b/src/System.Linq.Expressions/tests/Dynamic/ExpandoObjectProxyTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Dynamic.Tests @@ -16,14 +17,18 @@ private static Type GetDebugViewType(Type type) { var att = (DebuggerTypeProxyAttribute) - type.GetCustomAttributes().Single(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + type.GetCustomAttributes().SingleOrDefault(at => at.TypeId.Equals(typeof(DebuggerTypeProxyAttribute))); + if (att == null) + { + return null; + } string proxyName = att.ProxyTypeName; proxyName = proxyName.Substring(0, proxyName.IndexOf(',')); return type.GetTypeInfo().Assembly.GetType(proxyName); } private static object GetDebugViewObject(object obj) - => GetDebugViewType(obj.GetType()).GetConstructors().Single().Invoke(new[] {obj}); + => GetDebugViewType(obj.GetType())?.GetConstructors().Single().Invoke(new[] {obj}); private static IEnumerable> TestExpandos() { @@ -76,38 +81,55 @@ private static void AssertSameCollectionIgnoreOrder(ICollection expected, Assert.Contains(item, expected); } - [Theory] + [ConditionalTheory] [MemberData(nameof(KeyCollections))] [MemberData(nameof(ValueCollections))] public void ItemsAreRootHidden(object eo) { - PropertyInfo itemsProp = GetDebugViewObject(eo).GetType().GetProperty("Items"); + object view = GetDebugViewObject(eo); + if (view == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {eo}."); + } + PropertyInfo itemsProp = view.GetType().GetProperty("Items"); var browsable = (DebuggerBrowsableAttribute)itemsProp.GetCustomAttribute(typeof(DebuggerBrowsableAttribute)); Assert.Equal(DebuggerBrowsableState.RootHidden, browsable.State); } - [Theory, MemberData(nameof(KeyCollections))] + [ConditionalTheory, MemberData(nameof(KeyCollections))] public void KeyCollectionCorrectlyViewed(ICollection keys) { object view = GetDebugViewObject(keys); + if (view == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {keys}."); + } PropertyInfo itemsProp = view.GetType().GetProperty("Items"); string[] items = (string[])itemsProp.GetValue(view); AssertSameCollectionIgnoreOrder(keys, items); } - [Theory, MemberData(nameof(ValueCollections))] + [ConditionalTheory, MemberData(nameof(ValueCollections))] public void ValueCollectionCorrectlyViewed(ICollection keys) { object view = GetDebugViewObject(keys); + if (view == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {keys}."); + } PropertyInfo itemsProp = view.GetType().GetProperty("Items"); object[] items = (object[])itemsProp.GetValue(view); AssertSameCollectionIgnoreOrder(keys, items); } - [Theory, MemberData(nameof(OneOfEachCollection))] + [ConditionalTheory, MemberData(nameof(OneOfEachCollection))] public void ViewTypeThrowsOnNull(object collection) { Type debugViewType = GetDebugViewType(collection.GetType()); + if (debugViewType == null) + { + throw new SkipTestException($"Didn't find DebuggerTypeProxyAttribute on {collection.GetType()}."); + } ConstructorInfo constructor = debugViewType.GetConstructors().Single(); TargetInvocationException tie = Assert.Throws(() => constructor.Invoke(new object[] {null})); var ane = (ArgumentNullException)tie.InnerException; diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index 46af74f20413..111a53280da7 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -92,15 +92,48 @@ public static void Reverse(this System.Span span) { } public static int ToLowerInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } public static int ToUpper(this System.ReadOnlySpan source, System.Span destination, System.Globalization.CultureInfo culture) { throw null; } public static int ToUpperInvariant(this System.ReadOnlySpan source, System.Span destination) { throw null; } + public static System.Memory Trim(this System.Memory memory) { throw null; } + public static System.ReadOnlyMemory Trim(this System.ReadOnlyMemory memory) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, char trimChar) { throw null; } public static System.ReadOnlySpan Trim(this System.ReadOnlySpan span, System.ReadOnlySpan trimChars) { throw null; } + public static System.Span Trim(this System.Span span) { throw null; } + public static System.Memory TrimEnd(this System.Memory memory) { throw null; } + public static System.ReadOnlyMemory TrimEnd(this System.ReadOnlyMemory memory) { throw null; } public static System.ReadOnlySpan TrimEnd(this System.ReadOnlySpan span) { throw null; } public static System.ReadOnlySpan TrimEnd(this System.ReadOnlySpan span, char trimChar) { throw null; } public static System.ReadOnlySpan TrimEnd(this System.ReadOnlySpan span, System.ReadOnlySpan trimChars) { throw null; } + public static System.Span TrimEnd(this System.Span span) { throw null; } + public static System.Memory TrimEnd(this System.Memory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Memory TrimEnd(this System.Memory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory TrimEnd(this System.ReadOnlyMemory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory TrimEnd(this System.ReadOnlyMemory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan TrimEnd(this System.ReadOnlySpan memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan TrimEnd(this System.ReadOnlySpan memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.Span TrimEnd(this System.Span memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Span TrimEnd(this System.Span memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.Memory TrimStart(this System.Memory memory) { throw null; } + public static System.ReadOnlyMemory TrimStart(this System.ReadOnlyMemory memory) { throw null; } public static System.ReadOnlySpan TrimStart(this System.ReadOnlySpan span) { throw null; } public static System.ReadOnlySpan TrimStart(this System.ReadOnlySpan span, char trimChar) { throw null; } public static System.ReadOnlySpan TrimStart(this System.ReadOnlySpan span, System.ReadOnlySpan trimChars) { throw null; } + public static System.Span TrimStart(this System.Span span) { throw null; } + public static System.Memory TrimStart(this System.Memory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Memory TrimStart(this System.Memory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory TrimStart(this System.ReadOnlyMemory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory TrimStart(this System.ReadOnlyMemory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan TrimStart(this System.ReadOnlySpan memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan TrimStart(this System.ReadOnlySpan memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.Span TrimStart(this System.Span memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Span TrimStart(this System.Span memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.Memory Trim(this System.Memory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Memory Trim(this System.Memory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory Trim(this System.ReadOnlyMemory memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlyMemory Trim(this System.ReadOnlyMemory memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan Trim(this System.ReadOnlySpan memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.ReadOnlySpan Trim(this System.ReadOnlySpan memory, T trimElement) where T : System.IEquatable { throw null; } + public static System.Span Trim(this System.Span memory, System.ReadOnlySpan trimElements) where T : System.IEquatable { throw null; } + public static System.Span Trim(this System.Span memory, T trimElement) where T : System.IEquatable { throw null; } } public readonly partial struct SequencePosition : System.IEquatable { @@ -120,6 +153,20 @@ public static void Reverse(this System.Span span) { } } namespace System.Buffers { + public sealed partial class ArrayBufferWriter : System.Buffers.IBufferWriter + { + public ArrayBufferWriter() { } + public ArrayBufferWriter(int initialCapacity) { } + public int Capacity { get { throw null; } } + public int FreeCapacity { get { throw null; } } + public int WrittenCount { get { throw null; } } + public System.ReadOnlyMemory WrittenMemory { get { throw null; } } + public System.ReadOnlySpan WrittenSpan { get { throw null; } } + public void Advance(int count) { } + public void Clear() { } + public System.Memory GetMemory(int sizeHint = 0) { throw null; } + public System.Span GetSpan(int sizeHint = 0) { throw null; } + } public static partial class BuffersExtensions { public static void CopyTo(this in System.Buffers.ReadOnlySequence source, System.Span destination) { } diff --git a/src/System.Memory/src/ApiCompatBaseline.uapaot.txt b/src/System.Memory/src/ApiCompatBaseline.uapaot.txt new file mode 100644 index 000000000000..c921ccca6d1e --- /dev/null +++ b/src/System.Memory/src/ApiCompatBaseline.uapaot.txt @@ -0,0 +1,37 @@ +Compat issues with assembly System.Memory: +CannotRemoveBaseTypeOrInterface : Type 'System.Memory' does not implement interface 'System.IEquatable>' in the implementation but it does in the contract. +CannotRemoveBaseTypeOrInterface : Type 'System.ReadOnlyMemory' does not implement interface 'System.IEquatable>' in the implementation but it does in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Memory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.ReadOnlyMemory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Span)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Memory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Memory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.ReadOnlyMemory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.ReadOnlyMemory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.ReadOnlySpan, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.ReadOnlySpan, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Span, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.Trim(System.Span, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Memory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.ReadOnlyMemory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Span)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Memory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Memory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.ReadOnlyMemory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.ReadOnlyMemory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.ReadOnlySpan, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.ReadOnlySpan, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Span, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimEnd(System.Span, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Memory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.ReadOnlyMemory)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Span)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Memory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Memory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.ReadOnlyMemory, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.ReadOnlyMemory, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.ReadOnlySpan, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.ReadOnlySpan, T)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Span, System.ReadOnlySpan)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.MemoryExtensions.TrimStart(System.Span, T)' does not exist in the implementation but it does exist in the contract. +Total issues: 35 diff --git a/src/System.Memory/src/Configurations.props b/src/System.Memory/src/Configurations.props index 24f90bfd1544..5f6eee24b03d 100644 --- a/src/System.Memory/src/Configurations.props +++ b/src/System.Memory/src/Configurations.props @@ -2,9 +2,8 @@ uap-Windows_NT; + uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Memory/src/Resources/Strings.resx b/src/System.Memory/src/Resources/Strings.resx index 31ac00d9312b..1ac9cace8401 100644 --- a/src/System.Memory/src/Resources/Strings.resx +++ b/src/System.Memory/src/Resources/Strings.resx @@ -143,4 +143,7 @@ Unexpected segment type. + + Cannot advance past the end of the buffer, which has a size of {0}. + diff --git a/src/System.Memory/src/System.Memory.csproj b/src/System.Memory/src/System.Memory.csproj index 773dbae2cdb9..4afcbc51e8dd 100644 --- a/src/System.Memory/src/System.Memory.csproj +++ b/src/System.Memory/src/System.Memory.csproj @@ -3,8 +3,12 @@ {4BBC8F69-D03E-4432-AA8A-D458FA5B235A} true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + + + + @@ -29,6 +33,9 @@ Common\System\Collections\HashHelpers.cs + + Common\System\Buffers\ArrayBufferWriter.cs + diff --git a/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs new file mode 100644 index 000000000000..b6c730ff6c1e --- /dev/null +++ b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Byte.cs @@ -0,0 +1,367 @@ +// 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.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_Byte + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + Span _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + [Fact] + public static void WriteAndCopyToStream() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + using (var memStream = new MemoryStream(100)) + { + Assert.Equal(100, output.WrittenCount); + + ReadOnlySpan outputSpan = output.WrittenMemory.ToArray(); + + ReadOnlyMemory transientMemory = output.WrittenMemory; + ReadOnlySpan transientSpan = output.WrittenSpan; + + Assert.True(transientSpan.SequenceEqual(transientMemory.Span)); + + Assert.True(transientSpan[0] != 0); + + memStream.Write(transientSpan.ToArray(), 0, transientSpan.Length); + output.Clear(); + + Assert.True(transientSpan[0] == 0); + Assert.True(transientMemory.Span[0] == 0); + + Assert.Equal(0, output.WrittenCount); + byte[] streamOutput = memStream.ToArray(); + + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + + Assert.Equal(outputSpan.Length, streamOutput.Length); + Assert.True(outputSpan.SequenceEqual(streamOutput)); + } + } + + [Fact] + public static async Task WriteAndCopyToStreamAsync() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + using (var memStream = new MemoryStream(100)) + { + Assert.Equal(100, output.WrittenCount); + + ReadOnlyMemory outputMemory = output.WrittenMemory.ToArray(); + + ReadOnlyMemory transient = output.WrittenMemory; + + Assert.True(transient.Span[0] != 0); + + await memStream.WriteAsync(transient.ToArray(), 0, transient.Length); + output.Clear(); + + Assert.True(transient.Span[0] == 0); + + Assert.Equal(0, output.WrittenCount); + byte[] streamOutput = memStream.ToArray(); + + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenMemory.Span)); + + Assert.Equal(outputMemory.Length, streamOutput.Length); + Assert.True(outputMemory.Span.SequenceEqual(streamOutput)); + } + } + + private static void WriteData(IBufferWriter bufferWriter, int numBytes) + { + Span outputSpan = bufferWriter.GetSpan(numBytes); + Debug.Assert(outputSpan.Length >= numBytes); + var random = new Random(42); + + var data = new byte[numBytes]; + random.NextBytes(data); + data.CopyTo(outputSpan); + + bufferWriter.Advance(numBytes); + } + } +} diff --git a/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs new file mode 100644 index 000000000000..8b5cf11be8e5 --- /dev/null +++ b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.Char.cs @@ -0,0 +1,303 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_Char + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + private static void WriteData(IBufferWriter bufferWriter, int numChars) + { + Span outputSpan = bufferWriter.GetSpan(numChars); + Debug.Assert(outputSpan.Length >= numChars); + var random = new Random(42); + + var data = new char[numChars]; + + for (int i = 0; i < numChars; i++) + { + data[i] = (char)random.Next(0, char.MaxValue); + } + + data.CopyTo(outputSpan); + + bufferWriter.Advance(numChars); + } + } +} diff --git a/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs new file mode 100644 index 000000000000..cfb16abcec89 --- /dev/null +++ b/src/System.Memory/tests/ArrayBufferWriter/ArrayBufferWriterTests.String.cs @@ -0,0 +1,315 @@ +// 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.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using Xunit; + +namespace System.Buffers.Tests +{ + public static partial class ArrayBufferWriterTests_String + { + [Fact] + public static void ArrayBufferWriter_Ctor() + { + { + var output = new ArrayBufferWriter(); + Assert.True(output.FreeCapacity > 0); + Assert.True(output.Capacity > 0); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + var output = new ArrayBufferWriter(200); + Assert.True(output.FreeCapacity >= 200); + Assert.True(output.Capacity >= 200); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + } + + { + ArrayBufferWriter output = default; + Assert.Equal(null, output); + } + } + + [Fact] + public static void Invalid_Ctor() + { + Assert.Throws(() => new ArrayBufferWriter(0)); + Assert.Throws(() => new ArrayBufferWriter(-1)); + Assert.Throws(() => new ArrayBufferWriter(int.MaxValue)); + } + + [Fact] + public static void Clear() + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + WriteData(output, 2); + Assert.True(output.FreeCapacity < previousAvailable); + Assert.True(output.WrittenCount > 0); + Assert.False(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.False(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + output.Clear(); + Assert.Equal(0, output.WrittenCount); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(output.WrittenSpan)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + + [Fact] + public static void Advance() + { + { + var output = new ArrayBufferWriter(); + int capacity = output.Capacity; + Assert.Equal(capacity, output.FreeCapacity); + output.Advance(output.FreeCapacity); + Assert.Equal(capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + } + + { + var output = new ArrayBufferWriter(); + output.Advance(output.Capacity); + Assert.Equal(output.Capacity, output.WrittenCount); + Assert.Equal(0, output.FreeCapacity); + int previousCapacity = output.Capacity; + _ = output.GetSpan(); + Assert.True(output.Capacity > previousCapacity); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(10); + Assert.False(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.False(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + } + + [Fact] + public static void AdvanceZero() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Equal(2, output.WrittenCount); + ReadOnlyMemory previousMemory = output.WrittenMemory; + ReadOnlySpan previousSpan = output.WrittenSpan; + Assert.True(previousSpan.SequenceEqual(previousMemory.Span)); + output.Advance(0); + Assert.Equal(2, output.WrittenCount); + Assert.True(previousMemory.Span.SequenceEqual(output.WrittenMemory.Span)); + Assert.True(previousSpan.SequenceEqual(output.WrittenSpan)); + Assert.True(output.WrittenSpan.SequenceEqual(output.WrittenMemory.Span)); + } + + [Fact] + public static void InvalidAdvance() + { + { + var output = new ArrayBufferWriter(); + Assert.Throws(() => output.Advance(-1)); + Assert.Throws(() => output.Advance(output.Capacity + 1)); + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + Assert.Throws(() => output.Advance(output.FreeCapacity + 1)); + } + } + + public static bool IsX64 { get; } = IntPtr.Size >= 8; + + [ConditionalFact(nameof(IsX64))] + [OuterLoop] + public static void InvalidAdvance_Large() + { + try + { + { + var output = new ArrayBufferWriter(2_000_000_000); + WriteData(output, 1_000); + Assert.Throws(() => output.Advance(int.MaxValue)); + Assert.Throws(() => output.Advance(2_000_000_000 - 1_000 + 1)); + } + } + catch (OutOfMemoryException) { } + } + + [Fact] + public static void GetMemoryAndSpan() + { + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Span span = output.GetSpan(); + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length > 0); + Assert.True(memorySpan.Length > 0); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + } + + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + ReadOnlyMemory writtenSoFarMemory = output.WrittenMemory; + ReadOnlySpan writtenSoFar = output.WrittenSpan; + Assert.True(writtenSoFarMemory.Span.SequenceEqual(writtenSoFar)); + int previousAvailable = output.FreeCapacity; + Span span = output.GetSpan(500); + Assert.True(span.Length >= 500); + Assert.True(output.FreeCapacity >= 500); + Assert.True(output.FreeCapacity > previousAvailable); + + Assert.Equal(writtenSoFar.Length, output.WrittenCount); + Assert.False(writtenSoFar.SequenceEqual(span.Slice(0, output.WrittenCount))); + + Memory memory = output.GetMemory(); + Span memorySpan = memory.Span; + Assert.True(span.Length >= 500); + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(default, span[i]); + Assert.Equal(default, memorySpan[i]); + } + + memory = output.GetMemory(500); + memorySpan = memory.Span; + Assert.True(memorySpan.Length >= 500); + Assert.Equal(span.Length, memorySpan.Length); + for (int i = 0; i < memorySpan.Length; i++) + { + Assert.Equal(default, memorySpan[i]); + } + } + } + + [Fact] + public static void GetSpanShouldAtleastDoubleWhenGrowing() + { + var output = new ArrayBufferWriter(); + WriteData(output, 100); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + _ = output.GetSpan(previousAvailable + 1); + Assert.True(output.FreeCapacity >= previousAvailable * 2); + } + + [Fact] + public static void GetSpanOnlyGrowsAboveThreshold() + { + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + + { + var output = new ArrayBufferWriter(); + int previousAvailable = output.FreeCapacity; + + _ = output.GetSpan(previousAvailable); + Assert.Equal(previousAvailable, output.FreeCapacity); + + previousAvailable = output.FreeCapacity; + for (int i = 0; i < 10; i++) + { + _ = output.GetSpan(); + Assert.Equal(previousAvailable, output.FreeCapacity); + } + } + } + + [Fact] + public static void InvalidGetMemoryAndSpan() + { + var output = new ArrayBufferWriter(); + WriteData(output, 2); + Assert.Throws(() => output.GetSpan(-1)); + Assert.Throws(() => output.GetMemory(-1)); + } + + [Fact] + public static void MultipleCallsToGetSpan() + { + var output = new ArrayBufferWriter(300); + int previousAvailable = output.FreeCapacity; + Assert.True(previousAvailable >= 300); + Assert.True(output.Capacity >= 300); + Assert.Equal(previousAvailable, output.Capacity); + Span span = output.GetSpan(); + Assert.True(span.Length >= previousAvailable); + Assert.True(span.Length >= 256); + Span newSpan = output.GetSpan(); + Assert.Equal(span.Length, newSpan.Length); + + unsafe + { + void* pSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + void* pNewSpan = Unsafe.AsPointer(ref MemoryMarshal.GetReference(newSpan)); + Assert.Equal((IntPtr)pSpan, (IntPtr)pNewSpan); + } + + Assert.Equal(span.Length, output.GetSpan().Length); + } + + private static void WriteData(IBufferWriter bufferWriter, int numStrings) + { + Span outputSpan = bufferWriter.GetSpan(numStrings); + Debug.Assert(outputSpan.Length >= numStrings); + var random = new Random(42); + + var data = new string[numStrings]; + + for (int i = 0; i < numStrings; i++) + { + int length = random.Next(5, 10); + data[i] = GetRandomString(random, length, 32, 127); + } + + data.CopyTo(outputSpan); + + bufferWriter.Advance(numStrings); + } + + private static string GetRandomString(Random r, int length, int minCodePoint, int maxCodePoint) + { + StringBuilder sb = new StringBuilder(length); + while (length-- != 0) + { + sb.Append((char)r.Next(minCodePoint, maxCodePoint)); + } + return sb.ToString(); + } + } +} diff --git a/src/System.Memory/tests/Memory/Equality.cs b/src/System.Memory/tests/Memory/Equality.cs index 86f8d61b49ae..da2428431894 100644 --- a/src/System.Memory/tests/Memory/Equality.cs +++ b/src/System.Memory/tests/Memory/Equality.cs @@ -120,6 +120,111 @@ public static void DefaultMemoryCanBeBoxed() Assert.True(readOnlyMemory.Equals(memoryAsObject)); } + [Fact] + public static void EqualityThroughInterface_True() + { + int[] a = { 10, 11, 12, 13, 14 }; + Memory left = new Memory(a, 2, 3); + Memory right = new Memory(a, 2, 3); + IEquatable> leftAsEquatable = left; + IEquatable> rightAsEquatable = right; + + Assert.True(leftAsEquatable.Equals(right)); + Assert.True(rightAsEquatable.Equals(left)); + } + + [Fact] + public static void EqualityThroughInterface_Reflexivity() + { + int[] array = { 42, 43, 44, 45, 46 }; + Memory left = new Memory(array, 2, 3); + IEquatable> leftAsEquatable = left; + + Assert.True(leftAsEquatable.Equals(left)); + } + + [Fact] + public static void EqualityThroughInterface_IncludesLength() + { + int[] array = { 42, 43, 44, 45, 46 }; + Memory baseline = new Memory(array, 2, 3); + Memory equalRangeButLength = new Memory(array, 2, 2); + IEquatable> baselineAsEquatable = baseline; + IEquatable> anotherOneAsEquatable = equalRangeButLength; + + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); + Assert.False(anotherOneAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_IncludesBase() + { + int[] array = { 42, 43, 44, 45, 46 }; + Memory baseline = new Memory(array, 2, 3); + Memory equalLengthButRange = new Memory(array, 1, 3); + IEquatable> baselineAsEquatable = baseline; + IEquatable> anotherOneAsEquatable = equalLengthButRange; + + Assert.False(baselineAsEquatable.Equals(equalLengthButRange)); + Assert.False(anotherOneAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_ComparesRangeNotContent() + { + Memory baseline = new Memory(new [] { 1, 2, 3, 4, 5, 6 }, 2, 3); + Memory duplicate = new Memory(new [] { 1, 2, 3, 4, 5, 6 }, 2, 3); + IEquatable> baselineAsEquatable = baseline; + IEquatable> duplicateAsEquatable = duplicate; + + Assert.False(baselineAsEquatable.Equals(duplicate)); + Assert.False(duplicateAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_Strings() + { + string[] array = { "A", "B", "C", "D", "E", "F" }; + string[] anotherArray = { "A", "B", "C", "D", "E", "F" }; + + Memory baseline = new Memory(array, 2, 3); + IEquatable> baselineAsEquatable = baseline; + Memory equalRangeAndLength = new Memory(array, 2, 3); + Memory equalRangeButLength = new Memory(array, 2, 2); + Memory equalLengthButReference = new Memory(array, 3, 3); + Memory differentArraySegmentAsMemory = new Memory(anotherArray, 2, 3); + + Assert.True(baselineAsEquatable.Equals(baseline)); // Reflexivity + Assert.True(baselineAsEquatable.Equals(equalRangeAndLength)); // Range check & length check + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); // Length check + Assert.False(baselineAsEquatable.Equals(equalLengthButReference)); // Range check + + Assert.True(baseline.Span.SequenceEqual(differentArraySegmentAsMemory.Span)); + Assert.False(baselineAsEquatable.Equals(differentArraySegmentAsMemory)); // Doesn't check for content equality + } + + [Fact] + public static void EqualityThroughInterface_Chars() + { + char[] array = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' }; + char[] anotherArray = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' }; + + Memory baseline = new Memory(array, 2, 3); + IEquatable> baselineAsEquatable = baseline; + Memory equalRangeAndLength = new Memory(array, 2, 3); + Memory equalRangeButLength = new Memory(array, 2, 2); + Memory equalLengthButReference = new Memory(array, 3, 3); + Memory differentArraySegmentAsMemory = new Memory(anotherArray, 2, 3); + + Assert.True(baselineAsEquatable.Equals(baseline)); // Reflexivity + Assert.True(baselineAsEquatable.Equals(equalRangeAndLength)); // Range check & length check + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); // Length check + Assert.False(baselineAsEquatable.Equals(equalLengthButReference)); // Range check + + Assert.True(baseline.Span.SequenceEqual(differentArraySegmentAsMemory.Span)); + Assert.False(baselineAsEquatable.Equals(differentArraySegmentAsMemory)); // Doesn't check for content equality + } + [Theory] [MemberData(nameof(ValidArraySegments))] public static void MemoryReferencingSameMemoryAreEqualInEveryAspect(byte[] bytes, int start, int length) diff --git a/src/System.Memory/tests/Memory/IndexAndRange.cs b/src/System.Memory/tests/Memory/IndexAndRange.cs index e5bcf8958d64..b5746b06106f 100644 --- a/src/System.Memory/tests/Memory/IndexAndRange.cs +++ b/src/System.Memory/tests/Memory/IndexAndRange.cs @@ -22,11 +22,11 @@ public static void SlicingUsingIndexAndRangeTest() Assert.Equal(memory.Slice(i, a.Length - i), memory[range]); Assert.Equal(roMemory.Slice(i, a.Length - i), roMemory[range]); - Assert.Equal(memory.Slice(i), memory.Slice(Index.FromStart(i))); - Assert.Equal(roMemory.Slice(i), roMemory.Slice(Index.FromStart(i))); + Assert.Equal(memory.Slice(i), memory[i..]); + Assert.Equal(roMemory.Slice(i), roMemory[i..]); - Assert.Equal(memory.Slice(i, a.Length - i), memory.Slice(range)); - Assert.Equal(roMemory.Slice(i, a.Length - i), roMemory.Slice(range)); + Assert.Equal(memory.Slice(i, a.Length - i), memory[range]); + Assert.Equal(roMemory.Slice(i, a.Length - i), roMemory[range]); } range = new Range(Index.FromStart(0), Index.FromStart(a.Length + 1)); diff --git a/src/System.Memory/tests/Memory/Trim.cs b/src/System.Memory/tests/Memory/Trim.cs new file mode 100644 index 000000000000..537e154ccd1b --- /dev/null +++ b/src/System.Memory/tests/Memory/Trim.cs @@ -0,0 +1,427 @@ +// 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.Collections.Generic; +using Xunit; + +namespace System.MemoryTests +{ + public static partial class MemoryTests + { + [Theory] + [InlineData(new int[0], 1, new int[0])] + [InlineData(new int[] { 1 }, 1, new int[0])] + [InlineData(new int[] { 2 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, 1, new int[] { 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, 1, new int[] { 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, 2, new int[] { 1, 1, 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, 3, new int[] { 1, 1, 2, 1 })] + [InlineData(new int[] { 1, 1, 1, 2 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, 1, new int[0])] + public static void MemoryExtensions_TrimStart_Single(int[] values, int trim, int[] expected) + { + Memory memory = new Memory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [InlineData(new int[0], 1, new int[0])] + [InlineData(new int[] { 1 }, 1, new int[0])] + [InlineData(new int[] { 2 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, 1, new int[] { 1, 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 1, new int[] { 1, 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 2, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 3, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 2, 1, 1, 1 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, 1, new int[0])] + public static void MemoryExtensions_TrimEnd_Single(int[] values, int trim, int[] expected) + { + Memory memory = new Memory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [InlineData(new int[0], 1, new int[0])] + [InlineData(new int[] { 1 }, 1, new int[0])] + [InlineData(new int[] { 2 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 2, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, 3, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 2, 1, 1, 1 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 2 }, 1, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, 1, new int[0])] + public static void MemoryExtensions_Trim_Single(int[] values, int trim, int[] expected) + { + Memory memory = new Memory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [InlineData(new int[0], new int[0], new int[0])] + [InlineData(new int[0], new int[] { 1 }, new int[0])] + [InlineData(new int[] { 1 }, new int[0], new int[] { 1 })] + [InlineData(new int[] { 1 }, new int[] { 1 }, new int[0])] + [InlineData(new int[] { 2 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, new int[] { 1 }, new int[] { 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, new int[] { 1 }, new int[] { 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, new int[] { 2 }, new int[] { 1, 1, 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, new int[] { 3 }, new int[] { 1, 1, 2, 1 })] + [InlineData(new int[] { 1, 1, 2, 1 }, new int[] { 1, 2 }, new int[0])] + [InlineData(new int[] { 1, 1, 2, 3 }, new int[] { 1, 2 }, new int[] { 3 })] + [InlineData(new int[] { 1, 1, 2, 3 }, new int[] { 1, 2, 4 }, new int[] { 3 })] + [InlineData(new int[] { 1, 1, 1, 2 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, new int[] { 1 }, new int[0])] + public static void MemoryExtensions_TrimStart_Multi(int[] values, int[] trims, int[] expected) + { + Memory memory = new Memory(values).TrimStart(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimStart(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimStart(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimStart(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [InlineData(new int[0], new int[0], new int[0])] + [InlineData(new int[0], new int[] { 1 }, new int[0])] + [InlineData(new int[] { 1 }, new int[0], new int[] { 1 })] + [InlineData(new int[] { 1 }, new int[] { 1 }, new int[0])] + [InlineData(new int[] { 2 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, new int[] { 1 }, new int[] { 1, 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 1 }, new int[] { 1, 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 2 }, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 3 }, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 1, 2 }, new int[0])] + [InlineData(new int[] { 3, 2, 1, 1 }, new int[] { 1, 2 }, new int[] { 3 })] + [InlineData(new int[] { 3, 2, 1, 1 }, new int[] { 1, 2, 4 }, new int[] { 3 })] + [InlineData(new int[] { 2, 1, 1, 1 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, new int[] { 1 }, new int[0])] + public static void MemoryExtensions_TrimEnd_Multi(int[] values, int[] trims, int[] expected) + { + Memory memory = new Memory(values).TrimEnd(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimEnd(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimEnd(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimEnd(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [InlineData(new int[0], new int[0], new int[0])] + [InlineData(new int[0], new int[] { 1 }, new int[0])] + [InlineData(new int[] { 1 }, new int[0], new int[] { 1 })] + [InlineData(new int[] { 1 }, new int[] { 1 }, new int[0])] + [InlineData(new int[] { 2 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 2 }, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 3 }, new int[] { 1, 2, 1, 1 })] + [InlineData(new int[] { 1, 2, 1, 1 }, new int[] { 1, 2 }, new int[0])] + [InlineData(new int[] { 2, 1, 3, 2, 1, 1 }, new int[] { 1, 2 }, new int[] { 3 })] + [InlineData(new int[] { 2, 1, 3, 2, 1, 1 }, new int[] { 1, 2, 4 }, new int[] { 3 })] + [InlineData(new int[] { 1, 2, 1, 1, 1 }, new int[] { 1 }, new int[] { 2 })] + [InlineData(new int[] { 1, 1, 1, 1 }, new int[] { 1 }, new int[0])] + public static void MemoryExtensions_Trim_Multi(int[] values, int[] trims, int[] expected) + { + Memory memory = new Memory(values).Trim(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).Trim(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).Trim(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).Trim(trims); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + public sealed class Foo : IEquatable + { + public int Value { get; set; } + + public bool Equals(Foo other) + { + if (this == null && other == null) + return true; + if (other == null) + return false; + return Value == other.Value; + } + + public static implicit operator Foo(int value) => new Foo { Value = value }; + public static implicit operator int? (Foo foo) => foo?.Value; + } + + public static IEnumerable IdempotentValues => new object[][] + { + new object[1] { new Foo[] { } }, + new object[1] { new Foo[] { null, 1, 2, 3, null, 2, 1, null } } + }; + + [Theory] + [MemberData(nameof(IdempotentValues))] + public static void MemoryExtensions_TrimStart_Idempotent(Foo[] values) + { + Foo[] expected = values; + + Foo[] trim = null; + + Memory memory = new Memory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + + trim = new Foo[] { }; + + memory = new Memory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + rom = new ReadOnlyMemory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + span = new Span(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ros = new ReadOnlySpan(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [MemberData(nameof(IdempotentValues))] + public static void MemoryExtensions_TrimEnd_Idempotent(Foo[] values) + { + Foo[] expected = values; + + Foo[] trim = null; + + Memory memory = new Memory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + + trim = new Foo[] { }; + + memory = new Memory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + rom = new ReadOnlyMemory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + span = new Span(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ros = new ReadOnlySpan(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Theory] + [MemberData(nameof(IdempotentValues))] + public static void MemoryExtensions_Trim_Idempotent(Foo[] values) + { + Foo[] expected = values; + + Foo[] trim = null; + + Memory memory = new Memory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + + trim = new Foo[] { }; + + memory = new Memory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + rom = new ReadOnlyMemory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + span = new Span(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ros = new ReadOnlySpan(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_TrimStart_Single_Null() + { + var values = new Foo[] { null, null, 1, 2, null, null }; + Foo trim = null; + var expected = new Foo[] { 1, 2, null, null }; + + Memory memory = new Memory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_TrimStart_Multi_Null() + { + var values = new Foo[] { null, 1, 2, 3, null, 2, 1, null }; + var trim = new Foo[] { null, 1, 2 }; + var expected = new Foo[] { 3, null, 2, 1, null }; + + Memory memory = new Memory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimStart(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_TrimEnd_Single_Null() + { + var values = new Foo[] { null, null, 1, 2, null, null }; + Foo trim = null; + var expected = new Foo[] { null, null, 1, 2 }; + + Memory memory = new Memory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_TrimEnd_Multi_Null() + { + var values = new Foo[] { null, 1, 2, 3, null, 2, 1, null }; + var trim = new Foo[] { null, 1, 2 }; + var expected = new Foo[] { null, 1, 2, 3 }; + + Memory memory = new Memory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).TrimEnd(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_Trim_Single_Null() + { + var values = new Foo[] { null, null, 1, 2, null, null }; + Foo trim = null; + var expected = new Foo[] { 1, 2 }; + + Memory memory = new Memory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + + [Fact] + public static void MemoryExtensions_Trim_Multi_Null() + { + var values = new Foo[] { null, 1, 2, 3, null, 2, 1, null }; + var trim = new Foo[] { null, 1, 2 }; + var expected = new Foo[] { 3 }; + + Memory memory = new Memory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, memory.ToArray())); + + ReadOnlyMemory rom = new ReadOnlyMemory(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, rom.ToArray())); + + Span span = new Span(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, span.ToArray())); + + ReadOnlySpan ros = new ReadOnlySpan(values).Trim(trim); + Assert.True(System.Linq.Enumerable.SequenceEqual(expected, ros.ToArray())); + } + } +} diff --git a/src/System.Memory/tests/ReadOnlyMemory/Equality.cs b/src/System.Memory/tests/ReadOnlyMemory/Equality.cs index a6a53eeefa21..67fbc7ebb598 100644 --- a/src/System.Memory/tests/ReadOnlyMemory/Equality.cs +++ b/src/System.Memory/tests/ReadOnlyMemory/Equality.cs @@ -84,6 +84,112 @@ public static void EmptyMemoryNotUnified() Assert.True(right.Equals(left)); } + [Fact] + public static void EqualityThroughInterface_True() + { + int[] array = { 10, 11, 12, 13, 14 }; + ReadOnlyMemory left = new ReadOnlyMemory(array, 2, 3); + ReadOnlyMemory right = new ReadOnlyMemory(array, 2, 3); + IEquatable> leftAsEquatable = left; + IEquatable> rightAsEquatable = right; + + Assert.True(leftAsEquatable.Equals(right)); + Assert.True(rightAsEquatable.Equals(left)); + } + + [Fact] + public static void EqualityThroughInterface_Reflexivity() + { + int[] array = { 42, 43, 44, 45, 46 }; + ReadOnlyMemory left = new ReadOnlyMemory(array, 2, 3); + IEquatable> leftAsEquatable = left; + + Assert.True(leftAsEquatable.Equals(left)); + } + + [Fact] + public static void EqualityThroughInterface_IncludesLength() + { + int[] array = { 42, 43, 44, 45, 46 }; + ReadOnlyMemory baseline = new ReadOnlyMemory(array, 2, 3); + ReadOnlyMemory equalRangeButLength = new ReadOnlyMemory(array, 2, 2); + IEquatable> baselineAsEquatable = baseline; + IEquatable> anotherOneAsEquatable = equalRangeButLength; + + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); + Assert.False(anotherOneAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_IncludesBase() + { + int[] array = { 42, 43, 44, 45, 46 }; + ReadOnlyMemory baseline = new ReadOnlyMemory(array, 2, 3); + ReadOnlyMemory equalLengthButRange = new ReadOnlyMemory(array, 1, 3); + IEquatable> baselineAsEquatable = baseline; + IEquatable> anotherOneAsEquatable = equalLengthButRange; + + Assert.False(baselineAsEquatable.Equals(equalLengthButRange)); + Assert.False(anotherOneAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_ComparesRangeNotContent() + { + ReadOnlyMemory baseline = new ReadOnlyMemory(new[] { 1, 2, 3, 4, 5, 6 }, 2, 3); + ReadOnlyMemory duplicate = new ReadOnlyMemory(new[] { 1, 2, 3, 4, 5, 6 }, 2, 3); + IEquatable> baselineAsEquatable = baseline; + IEquatable> duplicateAsEquatable = duplicate; + + Assert.False(baselineAsEquatable.Equals(duplicate)); + Assert.False(duplicateAsEquatable.Equals(baseline)); + } + + [Fact] + public static void EqualityThroughInterface_Strings() + { + string[] array = { "A", "B", "C", "D", "E", "F" }; + string[] anotherArray = { "A", "B", "C", "D", "E", "F" }; + + ReadOnlyMemory baseline = new ReadOnlyMemory(array, 2, 3); + IEquatable> baselineAsEquatable = baseline; + ReadOnlyMemory equalRangeAndLength = new ReadOnlyMemory(array, 2, 3); + ReadOnlyMemory equalRangeButLength = new ReadOnlyMemory(array, 2, 2); + ReadOnlyMemory equalLengthButReference = new ReadOnlyMemory(array, 3, 3); + ReadOnlyMemory differentArraySegmentAsMemory = new ReadOnlyMemory(anotherArray, 2, 3); + + Assert.True(baselineAsEquatable.Equals(baseline)); // Reflexivity + Assert.True(baselineAsEquatable.Equals(equalRangeAndLength)); // Range check & length check + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); // Length check + Assert.False(baselineAsEquatable.Equals(equalLengthButReference)); // Range check + + Assert.True(baseline.Span.SequenceEqual(differentArraySegmentAsMemory.Span)); + Assert.False(baselineAsEquatable.Equals(differentArraySegmentAsMemory)); // Doesn't check for content equality + } + + [Fact] + public static void EqualityThroughInterface_Chars() + { + char[] array = { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' }; + string str = new string(array); // To prevent both string literals being interned therefore having same reference + string anotherStr = new string(array); + + ReadOnlyMemory baseline = str.AsMemory(2, 3); + IEquatable> baselineAsEquatable = baseline; + ReadOnlyMemory equalRangeAndLength = str.AsMemory(2, 3); + ReadOnlyMemory equalRangeButLength = str.AsMemory(2, 2); + ReadOnlyMemory equalLengthButReference = str.AsMemory(3, 3); + ReadOnlyMemory differentArraySegmentAsMemory = anotherStr.AsMemory(2, 3); + + Assert.True(baselineAsEquatable.Equals(baseline)); // Reflexivity + Assert.True(baselineAsEquatable.Equals(equalRangeAndLength)); // Range check & length check + Assert.False(baselineAsEquatable.Equals(equalRangeButLength)); // Length check + Assert.False(baselineAsEquatable.Equals(equalLengthButReference)); // Range check + + Assert.True(baseline.Span.SequenceEqual(differentArraySegmentAsMemory.Span)); + Assert.False(baselineAsEquatable.Equals(differentArraySegmentAsMemory)); // Doesn't check for content equality + } + [Theory] [MemberData(nameof(ValidArraySegments))] public static void MemoryReferencingSameMemoryAreEqualInEveryAspect(byte[] bytes, int start, int length) diff --git a/src/System.Memory/tests/Span/Indexer.cs b/src/System.Memory/tests/Span/Indexer.cs index 5de9c13f9fae..f37a7dd40317 100644 --- a/src/System.Memory/tests/Span/Indexer.cs +++ b/src/System.Memory/tests/Span/Indexer.cs @@ -56,20 +56,21 @@ public static void SlicingUsingIndexAndRangeTest() for (int i = 0; i < span.Length; i++) { - Assert.True(span.Slice(i) == span.Slice(Index.FromStart(i))); - Assert.True(span.Slice(span.Length - i - 1) == span.Slice(Index.FromEnd(i + 1))); + Assert.True(span.Slice(i) == span[i..]); + Assert.True(span.Slice(span.Length - i - 1) == span[^(i + 1)..]); - Assert.True(roSpan.Slice(i) == roSpan.Slice(Index.FromStart(i))); - Assert.True(roSpan.Slice(roSpan.Length - i - 1) == roSpan.Slice(Index.FromEnd(i + 1))); + Assert.True(roSpan.Slice(i) == roSpan[i..]); + Assert.True(roSpan.Slice(roSpan.Length - i - 1) == roSpan[^(i + 1)..]); range = new Range(Index.FromStart(i), Index.FromEnd(0)); - Assert.True(span.Slice(i, span.Length - i) == span.Slice(range)); - Assert.True(roSpan.Slice(i, roSpan.Length - i) == roSpan.Slice(range)); + Assert.True(span.Slice(i, span.Length - i) == span[range]); + Assert.True(roSpan.Slice(i, roSpan.Length - i) == roSpan[range]); } range = new Range(Index.FromStart(0), Index.FromStart(span.Length + 1)); - Assert.Throws(() => new Span(s.ToCharArray()).Slice(range)); - Assert.Throws(() => s.AsSpan().Slice(range)); + + Assert.Throws(delegate() { var spp = new Span(s.ToCharArray())[range]; }); + Assert.Throws(delegate() { var spp = s.AsSpan()[range]; }); } } } \ No newline at end of file diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 213fa817f516..ac762985143e 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -9,6 +9,9 @@ + + + @@ -184,6 +187,7 @@ + diff --git a/src/System.Net.Http/src/System.Net.Http.csproj b/src/System.Net.Http/src/System.Net.Http.csproj index 6467d62b857a..70ad122051c9 100644 --- a/src/System.Net.Http/src/System.Net.Http.csproj +++ b/src/System.Net.Http/src/System.Net.Http.csproj @@ -127,7 +127,6 @@ - @@ -143,6 +142,7 @@ + @@ -157,6 +157,7 @@ + Common\CoreLib\System\Collections\Concurrent\ConcurrentQueueSegment.cs @@ -214,7 +215,6 @@ - Common\System\Net\ContextAwareResult.Unix.cs @@ -256,6 +256,49 @@ Common\System\Net\Security\Unix\SafeDeleteNegoContext.cs + + + + + + + + Common\Interop\OSX\Interop.CoreFoundation.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFArray.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFData.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFDictionary.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFProxy.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFUrl.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFString.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFString.cs + + + Common\Interop\OSX\Interop.CoreFoundation.CFError.cs + + + Common\Interop\OSX\Interop.RunLoop.cs + + + Common\Interop\OSX\Interop.Libraries.cs + + + Common\Microsoft\Win32\SafeHandles\SafeCreateHandle.OSX.cs + + @@ -612,7 +655,7 @@ - + diff --git a/src/System.Net.Http/src/System/Net/Http/Headers/ContentDispositionHeaderValue.cs b/src/System.Net.Http/src/System/Net/Http/Headers/ContentDispositionHeaderValue.cs index b2f2aabf677f..d38085a0e212 100644 --- a/src/System.Net.Http/src/System/Net/Http/Headers/ContentDispositionHeaderValue.cs +++ b/src/System.Net.Http/src/System/Net/Http/Headers/ContentDispositionHeaderValue.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Text; namespace System.Net.Http.Headers @@ -425,7 +424,7 @@ private void SetName(string parameter, string value) } // Returns input for decoding failures, as the content might not be encoded. - private string EncodeAndQuoteMime(string input) + private static string EncodeAndQuoteMime(string input) { string result = input; bool needsQuotes = false; @@ -441,7 +440,7 @@ private string EncodeAndQuoteMime(string input) throw new ArgumentException(SR.Format(CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, input)); } - else if (RequiresEncoding(result)) + else if (HeaderUtilities.ContainsNonAscii(result)) { needsQuotes = true; // Encoded data must always be quoted, the equals signs are invalid in tokens. result = EncodeMime(result); // =?utf-8?B?asdfasdfaesdf?= @@ -460,7 +459,7 @@ private string EncodeAndQuoteMime(string input) } // Returns true if the value starts and ends with a quote. - private bool IsQuoted(ReadOnlySpan value) + private static bool IsQuoted(ReadOnlySpan value) { return value.Length > 1 && @@ -468,23 +467,8 @@ private bool IsQuoted(ReadOnlySpan value) value[value.Length - 1] == '"'; } - // tspecials are required to be in a quoted string. Only non-ascii needs to be encoded. - private bool RequiresEncoding(string input) - { - Debug.Assert(input != null); - - foreach (char c in input) - { - if ((int)c > 0x7f) - { - return true; - } - } - return false; - } - // Encode using MIME encoding. - private string EncodeMime(string input) + private static string EncodeMime(string input) { byte[] buffer = Encoding.UTF8.GetBytes(input); string encodedName = Convert.ToBase64String(buffer); @@ -492,7 +476,7 @@ private string EncodeMime(string input) } // Attempt to decode MIME encoded strings. - private bool TryDecodeMime(string input, out string output) + private static bool TryDecodeMime(string input, out string output) { Debug.Assert(input != null); @@ -535,7 +519,7 @@ private bool TryDecodeMime(string input, out string output) // Attempt to decode using RFC 5987 encoding. // encoding'language'my%20string - private bool TryDecode5987(string input, out string output) + private static bool TryDecode5987(string input, out string output) { output = null; diff --git a/src/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs b/src/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs index 73050a262681..1952e3f129df 100644 --- a/src/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs +++ b/src/System.Net.Http/src/System/Net/Http/Headers/HeaderUtilities.cs @@ -2,6 +2,7 @@ // 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.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -65,58 +66,63 @@ internal static void SetQuality(ObjectCollection parameter } } - // Encode a string using RFC 5987 encoding. - // encoding'lang'PercentEncodedSpecials - internal static string Encode5987(string input) + internal static bool ContainsNonAscii(string input) { - string output; - IsInputEncoded5987(input, out output); + Debug.Assert(input != null); - return output; + foreach (char c in input) + { + if ((int)c > 0x7f) + { + return true; + } + } + return false; } - internal static bool IsInputEncoded5987(string input, out string output) + // Encode a string using RFC 5987 encoding. + // encoding'lang'PercentEncodedSpecials + internal static string Encode5987(string input) { // Encode a string using RFC 5987 encoding. // encoding'lang'PercentEncodedSpecials - bool wasEncoded = false; StringBuilder builder = StringBuilderCache.Acquire(); + byte[] utf8bytes = ArrayPool.Shared.Rent(Encoding.UTF8.GetMaxByteCount(input.Length)); + int utf8length = Encoding.UTF8.GetBytes(input, 0, input.Length, utf8bytes, 0); + builder.Append("utf-8\'\'"); - foreach (char c in input) + for (int i = 0; i < utf8length; i++) { + byte utf8byte = utf8bytes[i]; + // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" // ; token except ( "*" / "'" / "%" ) - if (c > 0x7F) // Encodes as multiple utf-8 bytes + if (utf8byte > 0x7F) // Encodes as multiple utf-8 bytes { - byte[] bytes = Encoding.UTF8.GetBytes(c.ToString()); - foreach (byte b in bytes) - { - AddHexEscaped((char)b, builder); - wasEncoded = true; - } + AddHexEscaped(utf8byte, builder); } - else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%') + else if (!HttpRuleParser.IsTokenChar((char)utf8byte) || utf8byte == '*' || utf8byte == '\'' || utf8byte == '%') { // ASCII - Only one encoded byte. - AddHexEscaped(c, builder); - wasEncoded = true; + AddHexEscaped(utf8byte, builder); } else { - builder.Append(c); + builder.Append((char)utf8byte); } } - output = StringBuilderCache.GetStringAndRelease(builder); - return wasEncoded; + Array.Clear(utf8bytes, 0, utf8length); + ArrayPool.Shared.Return(utf8bytes); + + return StringBuilderCache.GetStringAndRelease(builder); } /// Transforms an ASCII character into its hexadecimal representation, adding the characters to a StringBuilder. - private static void AddHexEscaped(char c, StringBuilder destination) + private static void AddHexEscaped(byte c, StringBuilder destination) { Debug.Assert(destination != null); - Debug.Assert(c <= 0xFF); destination.Append('%'); destination.Append(s_hexUpperChars[(c & 0xf0) >> 4]); @@ -285,7 +291,7 @@ internal static int GetNextNonEmptyOrWhitespaceIndex(string input, int startInde return current; } - internal static DateTimeOffset? GetDateTimeOffsetValue(HeaderDescriptor descriptor, HttpHeaders store) + internal static DateTimeOffset? GetDateTimeOffsetValue(HeaderDescriptor descriptor, HttpHeaders store, DateTimeOffset? defaultValue = null) { Debug.Assert(store != null); @@ -294,6 +300,11 @@ internal static int GetNextNonEmptyOrWhitespaceIndex(string input, int startInde { return (DateTimeOffset)storedValue; } + else if (defaultValue != null && store.Contains(descriptor)) + { + return defaultValue; + } + return null; } diff --git a/src/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs b/src/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs index 974de64e83a6..c4bfb4ff3bee 100644 --- a/src/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs +++ b/src/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs @@ -133,7 +133,7 @@ public MediaTypeHeaderValue ContentType public DateTimeOffset? Expires { - get { return HeaderUtilities.GetDateTimeOffsetValue(KnownHeaders.Expires.Descriptor, this); } + get { return HeaderUtilities.GetDateTimeOffsetValue(KnownHeaders.Expires.Descriptor, this, DateTimeOffset.MinValue); } set { SetOrRemoveParsedValue(KnownHeaders.Expires.Descriptor, value); } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ArrayBuffer.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ArrayBuffer.cs index 7d82ab476c19..74eee0da03c3 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ArrayBuffer.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ArrayBuffer.cs @@ -59,6 +59,8 @@ public void Dispose() public Memory ActiveMemory => new Memory(_bytes, _activeStart, _availableStart - _activeStart); public Memory AvailableMemory => new Memory(_bytes, _availableStart, _bytes.Length - _availableStart); + public int Capacity => _bytes.Length; + public void Discard(int byteCount) { Debug.Assert(byteCount <= ActiveSpan.Length, $"Expected {byteCount} <= {ActiveSpan.Length}"); diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs index b156134285e9..b65b240e80f6 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AuthenticationHelper.Digest.cs @@ -91,9 +91,9 @@ public static async Task GetDigestTokenForCredential(NetworkCredential c } else { - string usernameStar; - if (HeaderUtilities.IsInputEncoded5987(credential.UserName, out usernameStar)) + if (HeaderUtilities.ContainsNonAscii(credential.UserName)) { + string usernameStar = HeaderUtilities.Encode5987(credential.UserName); sb.AppendKeyValue(UsernameStar, usernameStar, includeQuotes: false); } else @@ -408,6 +408,9 @@ private unsafe void Parse(string challenge) internal static class StringBuilderExtensions { + // Characters that require escaping in quoted string + private static readonly char[] SpecialCharacters = new[] { '"', '\\' }; + public static void AppendKeyValue(this StringBuilder sb, string key, string value, bool includeQuotes = true, bool includeComma = true) { sb.Append(key); @@ -415,12 +418,29 @@ public static void AppendKeyValue(this StringBuilder sb, string key, string valu if (includeQuotes) { sb.Append('"'); + int lastSpecialIndex = 0; + int specialIndex; + while (true) + { + specialIndex = value.IndexOfAny(SpecialCharacters, lastSpecialIndex); + if (specialIndex >= 0) + { + sb.Append(value, lastSpecialIndex, specialIndex - lastSpecialIndex); + sb.Append('\\'); + sb.Append(value[specialIndex]); + lastSpecialIndex = specialIndex + 1; + } + else + { + sb.Append(value, lastSpecialIndex, value.Length - lastSpecialIndex); + break; + } + } + sb.Append('"'); } - - sb.Append(value); - if (includeQuotes) + else { - sb.Append('"'); + sb.Append(value); } if (includeComma) diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs index d96546c59013..13de7c60b768 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ChunkedEncodingReadStream.cs @@ -5,7 +5,6 @@ using System.Buffers.Text; using System.Diagnostics; using System.IO; -using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -36,6 +35,65 @@ public ChunkedEncodingReadStream(HttpConnection connection, HttpResponseMessage _response = response; } + public override int Read(Span buffer) + { + if (_connection == null || buffer.Length == 0) + { + // Response body fully consumed or the caller didn't ask for any data. + return 0; + } + + // Try to consume from data we already have in the buffer. + int bytesRead = ReadChunksFromConnectionBuffer(buffer, cancellationRegistration: default); + if (bytesRead > 0) + { + return bytesRead; + } + + // Nothing available to consume. Fall back to I/O. + while (true) + { + if (_connection == null) + { + // Fully consumed the response in ReadChunksFromConnectionBuffer. + return 0; + } + + if (_state == ParsingState.ExpectChunkData && + buffer.Length >= _connection.ReadBufferSize && + _chunkBytesRemaining >= (ulong)_connection.ReadBufferSize) + { + // As an optimization, we skip going through the connection's read buffer if both + // the remaining chunk data and the buffer are both at least as large + // as the connection buffer. That avoids an unnecessary copy while still reading + // the maximum amount we'd otherwise read at a time. + Debug.Assert(_connection.RemainingBuffer.Length == 0); + bytesRead = _connection.Read(buffer.Slice(0, (int)Math.Min((ulong)buffer.Length, _chunkBytesRemaining))); + if (bytesRead == 0) + { + throw new IOException(SR.net_http_invalid_response); + } + _chunkBytesRemaining -= (ulong)bytesRead; + if (_chunkBytesRemaining == 0) + { + _state = ParsingState.ExpectChunkTerminator; + } + return bytesRead; + } + + // We're only here if we need more data to make forward progress. + _connection.Fill(); + + // Now that we have more, see if we can get any response data, and if + // we can we're done. + int bytesCopied = ReadChunksFromConnectionBuffer(buffer, cancellationRegistration: default); + if (bytesCopied > 0) + { + return bytesCopied; + } + } + } + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs index 1506cdf2b8ce..8960ad2d8e1b 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectionCloseReadStream.cs @@ -16,6 +16,25 @@ public ConnectionCloseReadStream(HttpConnection connection) : base(connection) { } + public override int Read(Span buffer) + { + if (_connection == null || buffer.Length == 0) + { + // Response body fully consumed or the caller didn't ask for any data + return 0; + } + + int bytesRead = _connection.Read(buffer); + if (bytesRead == 0) + { + // We cannot reuse this connection, so close it. + _connection.Dispose(); + _connection = null; + } + + return bytesRead; + } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); @@ -62,7 +81,6 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation // We cannot reuse this connection, so close it. _connection.Dispose(); _connection = null; - return 0; } return bytesRead; diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs index bd2727fd79d8..39e1440a317f 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ContentLengthReadStream.cs @@ -21,6 +21,40 @@ public ContentLengthReadStream(HttpConnection connection, ulong contentLength) : _contentBytesRemaining = contentLength; } + public override int Read(Span buffer) + { + if (_connection == null || buffer.Length == 0) + { + // Response body fully consumed or the caller didn't ask for any data. + return 0; + } + + Debug.Assert(_contentBytesRemaining > 0); + if ((ulong)buffer.Length > _contentBytesRemaining) + { + buffer = buffer.Slice(0, (int)_contentBytesRemaining); + } + + int bytesRead = _connection.Read(buffer); + if (bytesRead <= 0) + { + // Unexpected end of response stream. + throw new IOException(SR.net_http_invalid_response); + } + + Debug.Assert((ulong)bytesRead <= _contentBytesRemaining); + _contentBytesRemaining -= (ulong)bytesRead; + + if (_contentBytesRemaining == 0) + { + // End of response body + _connection.CompleteResponse(); + _connection = null; + } + + return bytesRead; + } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CreditManager.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CreditManager.cs index 6b5f9228b103..3fd0da14c8e7 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CreditManager.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/CreditManager.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace System.Net.Http @@ -13,7 +14,7 @@ internal sealed class CreditManager : IDisposable private struct Waiter { public int Amount; - public TaskCompletionSource TaskCompletionSource; + public TaskCompletionSourceWithCancellation TaskCompletionSource; } private int _current; @@ -37,7 +38,7 @@ private object SyncObject } } - public ValueTask RequestCreditAsync(int amount) + public ValueTask RequestCreditAsync(int amount, CancellationToken cancellationToken) { lock (SyncObject) { @@ -55,16 +56,21 @@ public ValueTask RequestCreditAsync(int amount) return new ValueTask(granted); } - var tcs = new TaskCompletionSource(TaskContinuationOptions.RunContinuationsAsynchronously); + // Uses RunContinuationsAsynchronously internally. + var tcs = new TaskCompletionSourceWithCancellation(); if (_waiters == null) { _waiters = new Queue(); } - _waiters.Enqueue(new Waiter { Amount = amount, TaskCompletionSource = tcs }); + Waiter waiter = new Waiter { Amount = amount, TaskCompletionSource = tcs }; - return new ValueTask(tcs.Task); + _waiters.Enqueue(waiter); + + return new ValueTask(cancellationToken.CanBeCanceled ? + tcs.WaitWithCancellationAsync(cancellationToken) : + tcs.Task); } } @@ -92,8 +98,12 @@ public void AdjustCredit(int amount) while (_current > 0 && _waiters.TryDequeue(out Waiter waiter)) { int granted = Math.Min(waiter.Amount, _current); - _current -= granted; - waiter.TaskCompletionSource.SetResult(granted); + + // Ensure that we grant credit only if the task has not been canceled. + if (waiter.TaskCompletionSource.TrySetResult(granted)) + { + _current -= granted; + } } } } @@ -114,7 +124,7 @@ public void Dispose() { while (_waiters.TryDequeue(out Waiter waiter)) { - waiter.TaskCompletionSource.SetException(new ObjectDisposedException(nameof(CreditManager))); + waiter.TaskCompletionSource.TrySetException(new ObjectDisposedException(nameof(CreditManager))); } } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs index aa08a5b5f9e8..6d154ea52428 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/EmptyReadStream.cs @@ -2,12 +2,13 @@ // 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.IO; using System.Threading; using System.Threading.Tasks; namespace System.Net.Http { - internal sealed class EmptyReadStream : BaseAsyncStream + internal sealed class EmptyReadStream : HttpBaseStream { internal static EmptyReadStream Instance { get; } = new EmptyReadStream(); @@ -19,16 +20,20 @@ private EmptyReadStream() { } protected override void Dispose(bool disposing) { /* nop */ } public override void Close() { /* nop */ } - public override int ReadByte() => -1; - public override int Read(Span buffer) => 0; public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled(cancellationToken)) : new ValueTask(0); - public override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + ValidateCopyToArgs(this, destination, bufferSize); + return NopAsync(cancellationToken); + } - public override Task FlushAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); + public override void Write(ReadOnlySpan buffer) => throw new NotSupportedException(SR.net_http_content_readonly_stream); + + public override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HPack/StaticTable.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HPack/StaticTable.cs index cf64ac7e6ffb..ff0c45ef5fc7 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HPack/StaticTable.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HPack/StaticTable.cs @@ -86,11 +86,12 @@ private static HeaderField CreateHeaderField(string name, string value) => value.Length != 0 ? Encoding.ASCII.GetBytes(value) : Array.Empty()); // Values for encoding. - // Unused values are omitted, so entries like ":scheme: http" are not included. + // Unused values are omitted. public const int Authority = 1; public const int MethodGet = 2; public const int MethodPost = 3; public const int PathSlash = 4; + public const int SchemeHttp = 6; public const int SchemeHttps = 7; public const int AcceptCharset = 15; public const int AcceptEncoding = 16; diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index c19ac0378308..213e0c0a3d51 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -19,7 +19,7 @@ namespace System.Net.Http internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable { private readonly HttpConnectionPool _pool; - private readonly SslStream _stream; + private readonly Stream _stream; // NOTE: These are mutable structs; do not make these readonly. private ArrayBuffer _incomingBuffer; @@ -31,6 +31,7 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private readonly Dictionary _httpStreams; private readonly SemaphoreSlim _writerLock; + private readonly SemaphoreSlim _headerSerializationLock; private readonly CreditManager _connectionWindow; private readonly CreditManager _concurrentStreams; @@ -41,9 +42,16 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable private int _maxConcurrentStreams; private int _pendingWindowUpdate; private int _idleSinceTickCount; + private int _pendingWriters; private bool _disposed; + // If an in-progress write is canceled we need to be able to immediately + // report a cancellation to the user, but also block the connection until + // the write completes. We avoid actually canceling the write, as we would + // then have to close the whole connection. + private Task _inProgressWrite = null; + private const int MaxStreamId = int.MaxValue; private static readonly byte[] s_http2ConnectionPreface = Encoding.ASCII.GetBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"); @@ -65,7 +73,12 @@ internal sealed partial class Http2Connection : HttpConnectionBase, IDisposable // rather than just increase the threshold. private const int ConnectionWindowThreshold = ConnectionWindowSize / 8; - public Http2Connection(HttpConnectionPool pool, SslStream stream) + // When buffering outgoing writes, we will automatically buffer up to this number of bytes. + // Single writes that are larger than the buffer can cause the buffer to expand beyond + // this value, so this is not a hard maximum size. + private const int UnflushedOutgoingBufferSize = 32 * 1024; + + public Http2Connection(HttpConnectionPool pool, Stream stream) { _pool = pool; _stream = stream; @@ -78,6 +91,7 @@ public Http2Connection(HttpConnectionPool pool, SslStream stream) _httpStreams = new Dictionary(); _writerLock = new SemaphoreSlim(1, 1); + _headerSerializationLock = new SemaphoreSlim(1, 1); _connectionWindow = new CreditManager(DefaultInitialWindowSize); _concurrentStreams = new CreditManager(int.MaxValue); @@ -458,7 +472,7 @@ private void ProcessSettingsFrame(FrameHeader frameHeader) // Send acknowledgement // Don't wait for completion, which could happen asynchronously. - ValueTask ignored = SendSettingsAckAsync(); + Task ignored = SendSettingsAckAsync(); } } @@ -527,7 +541,7 @@ private void ProcessPingFrame(FrameHeader frameHeader) // Send PING ACK // Don't wait for completion, which could happen asynchronously. - ValueTask ignored = SendPingAckAsync(_incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength)); + Task ignored = SendPingAckAsync(_incomingBuffer.ActiveMemory.Slice(0, FrameHeader.PingLength)); _incomingBuffer.Discard(frameHeader.Length); } @@ -623,80 +637,116 @@ private void ProcessGoAwayFrame(FrameHeader frameHeader) _incomingBuffer.Discard(frameHeader.Length); } - private async ValueTask AcquireWriteLockAsync() + private async Task StartWriteAsync(int writeBytes, CancellationToken cancellationToken = default) { - await _writerLock.WaitAsync().ConfigureAwait(false); + await AcquireWriteLockAsync(cancellationToken).ConfigureAwait(false); - // If the connection has been aborted, then fail now instead of trying to send more data. - if (IsAborted()) + try { - throw new IOException(SR.net_http_invalid_response); - } - } + // If there is a pending write that was canceled while in progress, wait for it to complete. + if (_inProgressWrite != null) + { + await _inProgressWrite.ConfigureAwait(false); + _inProgressWrite = null; + } - private void ReleaseWriteLock() - { - // Currently, we always flush the write buffer before releasing the lock. - // If we change this in the future, we will need to revisit this assert. - Debug.Assert(_outgoingBuffer.ActiveMemory.IsEmpty); + int totalBufferLength = _outgoingBuffer.Capacity; + int activeBufferLength = _outgoingBuffer.ActiveSpan.Length; - _writerLock.Release(); + if (totalBufferLength >= UnflushedOutgoingBufferSize && + writeBytes >= totalBufferLength - activeBufferLength && + activeBufferLength > 0) + { + // If the buffer has already grown to 32k, does not have room for the next request, + // and is non-empty, flush the current contents to the wire. + await FlushOutgoingBytesAsync().ConfigureAwait(false); + } + + _outgoingBuffer.EnsureAvailableSpace(writeBytes); + } + catch + { + _writerLock.Release(); + throw; + } } - private async ValueTask SendSettingsAckAsync() + // This method handles flushing bytes to the wire. Writes here need to be atomic, so as to avoid + // killing the whole connection. Callers must hold the write lock, but can specify whether or not + // they want to release it. + private void FinishWrite(bool mustFlush) { - await AcquireWriteLockAsync().ConfigureAwait(false); + // We can't validate that we hold the semaphore, but we can at least validate that someone is + // holding it. + Debug.Assert(_writerLock.CurrentCount == 0); + try { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size); - WriteFrameHeader(new FrameHeader(0, FrameType.Settings, FrameFlags.Ack, 0)); + // We must flush if the caller requires it, or if there are no other pending writes. + if (mustFlush || _pendingWriters == 0) + { + Debug.Assert(_inProgressWrite == null); - await FlushOutgoingBytesAsync().ConfigureAwait(false); + _inProgressWrite = FlushOutgoingBytesAsync(); + } } finally { - ReleaseWriteLock(); + _writerLock.Release(); } } - private async ValueTask SendPingAckAsync(ReadOnlyMemory pingContent) + private async Task AcquireWriteLockAsync(CancellationToken cancellationToken) { - Debug.Assert(pingContent.Length == FrameHeader.PingLength); - - await AcquireWriteLockAsync().ConfigureAwait(false); - try + Task acquireLockTask = _writerLock.WaitAsync(cancellationToken); + if (!acquireLockTask.IsCompletedSuccessfully) { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + FrameHeader.PingLength); - WriteFrameHeader(new FrameHeader(FrameHeader.PingLength, FrameType.Ping, FrameFlags.Ack, 0)); - pingContent.CopyTo(_outgoingBuffer.AvailableMemory); - _outgoingBuffer.Commit(FrameHeader.PingLength); - - await FlushOutgoingBytesAsync().ConfigureAwait(false); + Interlocked.Increment(ref _pendingWriters); + try + { + await acquireLockTask.ConfigureAwait(false); + } + finally + { + Interlocked.Decrement(ref _pendingWriters); + } } - finally + + // If the connection has been aborted, then fail now instead of trying to send more data. + if (IsAborted()) { - ReleaseWriteLock(); + throw new IOException(SR.net_http_invalid_response); } } - private async Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode) + private async Task SendSettingsAckAsync() { - await AcquireWriteLockAsync().ConfigureAwait(false); - try - { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + FrameHeader.RstStreamLength); - WriteFrameHeader(new FrameHeader(FrameHeader.RstStreamLength, FrameType.RstStream, FrameFlags.None, streamId)); + await StartWriteAsync(FrameHeader.Size).ConfigureAwait(false); + WriteFrameHeader(new FrameHeader(0, FrameType.Settings, FrameFlags.Ack, 0)); - BinaryPrimitives.WriteInt32BigEndian(_outgoingBuffer.AvailableSpan, (int)errorCode); + FinishWrite(mustFlush: true); + } - _outgoingBuffer.Commit(FrameHeader.RstStreamLength); + private async Task SendPingAckAsync(ReadOnlyMemory pingContent) + { + Debug.Assert(pingContent.Length == FrameHeader.PingLength); - await FlushOutgoingBytesAsync().ConfigureAwait(false); - } - finally - { - ReleaseWriteLock(); - } + await StartWriteAsync(FrameHeader.Size + FrameHeader.PingLength).ConfigureAwait(false); + WriteFrameHeader(new FrameHeader(FrameHeader.PingLength, FrameType.Ping, FrameFlags.Ack, 0)); + pingContent.CopyTo(_outgoingBuffer.AvailableMemory); + _outgoingBuffer.Commit(FrameHeader.PingLength); + + FinishWrite(mustFlush: false); + } + + private async Task SendRstStreamAsync(int streamId, Http2ProtocolErrorCode errorCode) + { + await StartWriteAsync(FrameHeader.Size + FrameHeader.RstStreamLength).ConfigureAwait(false); + WriteFrameHeader(new FrameHeader(FrameHeader.RstStreamLength, FrameType.RstStream, FrameFlags.None, streamId)); + BinaryPrimitives.WriteInt32BigEndian(_outgoingBuffer.AvailableSpan, (int)errorCode); + _outgoingBuffer.Commit(FrameHeader.RstStreamLength); + + FinishWrite(mustFlush: true); } private static (ReadOnlyMemory first, ReadOnlyMemory rest) SplitBuffer(ReadOnlyMemory buffer, int maxSize) => @@ -849,7 +899,7 @@ private void WriteHeaders(HttpRequestMessage request) WriteIndexedHeader(StaticTable.MethodGet, normalizedMethod.Method); } - WriteIndexedHeader(StaticTable.SchemeHttps); + WriteIndexedHeader(_stream is SslStream ? StaticTable.SchemeHttps : StaticTable.SchemeHttp); if (request.HasHeaders && request.Headers.Host != null) { @@ -902,28 +952,32 @@ private void WriteHeaders(HttpRequestMessage request) } } - private async ValueTask SendHeadersAsync(HttpRequestMessage request) + private async ValueTask SendHeadersAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // Ensure we don't exceed the max concurrent streams setting. - await _concurrentStreams.RequestCreditAsync(1).ConfigureAwait(false); + await _concurrentStreams.RequestCreditAsync(1, cancellationToken).ConfigureAwait(false); - // Note, HEADERS and CONTINUATION frames must be together, so hold the writer lock across sending all of them. - // We also serialize usage of the header encoder and the header buffer this way. - // (If necessary, we could have a separate semaphore just for creating and encoding header blocks, - // and defer taking the actual _writerLock until we're ready to do the write below.) - await _writerLock.WaitAsync().ConfigureAwait(false); + // We serialize usage of the header encoder and the header buffer separately from the + // write lock + await _headerSerializationLock.WaitAsync(cancellationToken).ConfigureAwait(false); - Http2Stream http2Stream = AddStream(request); - int streamId = http2Stream.StreamId; + Http2Stream http2Stream = null; try { + http2Stream = AddStream(request); + int streamId = http2Stream.StreamId; + // Generate the entire header block, without framing, into the connection header buffer. WriteHeaders(request); ReadOnlyMemory remaining = _headerBuffer.ActiveMemory; Debug.Assert(remaining.Length > 0); + // Calculate the total number of bytes we're going to use (content + headers). + int totalSize = remaining.Length + (remaining.Length / FrameHeader.MaxLength) * FrameHeader.Size + + (remaining.Length % FrameHeader.MaxLength == 0 ? FrameHeader.Size : 0); + // Split into frames and send. ReadOnlyMemory current; (current, remaining) = SplitBuffer(remaining, FrameHeader.MaxLength); @@ -932,42 +986,47 @@ private async ValueTask SendHeadersAsync(HttpRequestMessage request (remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None) | (request.Content == null ? FrameFlags.EndStream : FrameFlags.None); - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + current.Length); + // Note, HEADERS and CONTINUATION frames must be together, so hold the writer lock across sending all of them. + await StartWriteAsync(totalSize).ConfigureAwait(false); + WriteFrameHeader(new FrameHeader(current.Length, FrameType.Headers, flags, streamId)); current.CopyTo(_outgoingBuffer.AvailableMemory); _outgoingBuffer.Commit(current.Length); - await FlushOutgoingBytesAsync().ConfigureAwait(false); - while (remaining.Length > 0) { (current, remaining) = SplitBuffer(remaining, FrameHeader.MaxLength); flags = (remaining.Length == 0 ? FrameFlags.EndHeaders : FrameFlags.None); - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + current.Length); WriteFrameHeader(new FrameHeader(current.Length, FrameType.Continuation, flags, streamId)); current.CopyTo(_outgoingBuffer.AvailableMemory); _outgoingBuffer.Commit(current.Length); - - await FlushOutgoingBytesAsync().ConfigureAwait(false); } + + // If this is not the end of the stream, we can put off flushing the buffer + // since we know that there are going to be data frames following. + FinishWrite(mustFlush: (flags & FrameFlags.EndStream) != 0); } catch { - http2Stream.Dispose(); + if (http2Stream != null) + { + RemoveStream(http2Stream); + http2Stream.Dispose(); + } throw; } finally { _headerBuffer.Discard(_headerBuffer.ActiveMemory.Length); - _writerLock.Release(); + _headerSerializationLock.Release(); } return http2Stream; } - private async ValueTask SendStreamDataAsync(int streamId, ReadOnlyMemory buffer) + private async Task SendStreamDataAsync(int streamId, ReadOnlyMemory buffer, CancellationToken cancellationToken) { ReadOnlyMemory remaining = buffer; @@ -975,63 +1034,53 @@ private async ValueTask SendStreamDataAsync(int streamId, ReadOnlyMemory b { int frameSize = Math.Min(remaining.Length, FrameHeader.MaxLength); - frameSize = await _connectionWindow.RequestCreditAsync(frameSize).ConfigureAwait(false); + // Once credit had been granted, we want to actually consume those bytes. + frameSize = await _connectionWindow.RequestCreditAsync(frameSize, cancellationToken).ConfigureAwait(false); ReadOnlyMemory current; (current, remaining) = SplitBuffer(remaining, frameSize); - await AcquireWriteLockAsync().ConfigureAwait(false); + // It's possible that a cancellation will occur while we wait for the write lock. In that case, we need to + // return the credit that we have acquired and don't plan to use. try { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + current.Length); - WriteFrameHeader(new FrameHeader(current.Length, FrameType.Data, FrameFlags.None, streamId)); - current.CopyTo(_outgoingBuffer.AvailableMemory); - _outgoingBuffer.Commit(current.Length); - - await FlushOutgoingBytesAsync().ConfigureAwait(false); + await StartWriteAsync(FrameHeader.Size + current.Length, cancellationToken).ConfigureAwait(false); } - finally + catch (OperationCanceledException) { - ReleaseWriteLock(); + _connectionWindow.AdjustCredit(frameSize); + throw; } + + WriteFrameHeader(new FrameHeader(current.Length, FrameType.Data, FrameFlags.None, streamId)); + current.CopyTo(_outgoingBuffer.AvailableMemory); + _outgoingBuffer.Commit(current.Length); + + FinishWrite(mustFlush: false); } } - private async ValueTask SendEndStreamAsync(int streamId) + private async Task SendEndStreamAsync(int streamId) { - await AcquireWriteLockAsync().ConfigureAwait(false); - try - { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size); - WriteFrameHeader(new FrameHeader(0, FrameType.Data, FrameFlags.EndStream, streamId)); + await StartWriteAsync(FrameHeader.Size).ConfigureAwait(false); - await FlushOutgoingBytesAsync().ConfigureAwait(false); - } - finally - { - ReleaseWriteLock(); - } + WriteFrameHeader(new FrameHeader(0, FrameType.Data, FrameFlags.EndStream, streamId)); + + FinishWrite(mustFlush: true); } - private async ValueTask SendWindowUpdateAsync(int streamId, int amount) + private async Task SendWindowUpdateAsync(int streamId, int amount) { Debug.Assert(amount > 0); - await _writerLock.WaitAsync().ConfigureAwait(false); - try - { - _outgoingBuffer.EnsureAvailableSpace(FrameHeader.Size + FrameHeader.WindowUpdateLength); + // We update both the connection-level and stream-level windows at the same time + await StartWriteAsync(FrameHeader.Size + FrameHeader.WindowUpdateLength).ConfigureAwait(false); - WriteFrameHeader(new FrameHeader(FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId)); - BinaryPrimitives.WriteInt32BigEndian(_outgoingBuffer.AvailableSpan, amount); - _outgoingBuffer.Commit(FrameHeader.WindowUpdateLength); + WriteFrameHeader(new FrameHeader(FrameHeader.WindowUpdateLength, FrameType.WindowUpdate, FrameFlags.None, streamId)); + BinaryPrimitives.WriteInt32BigEndian(_outgoingBuffer.AvailableSpan, amount); + _outgoingBuffer.Commit(FrameHeader.WindowUpdateLength); - await FlushOutgoingBytesAsync().ConfigureAwait(false); - } - finally - { - _writerLock.Release(); - } + FinishWrite(mustFlush: true); } private void ExtendWindow(int amount) @@ -1053,7 +1102,7 @@ private void ExtendWindow(int amount) _pendingWindowUpdate = 0; } - ValueTask ignored = SendWindowUpdateAsync(0, windowUpdateSize); + Task ignored = SendWindowUpdateAsync(0, windowUpdateSize); } private void WriteFrameHeader(FrameHeader frameHeader) @@ -1295,10 +1344,10 @@ public sealed override async Task SendAsync(HttpRequestMess try { // Send headers - http2Stream = await SendHeadersAsync(request).ConfigureAwait(false); + http2Stream = await SendHeadersAsync(request, cancellationToken).ConfigureAwait(false); // Send request body, if any - await http2Stream.SendRequestBodyAsync().ConfigureAwait(false); + await http2Stream.SendRequestBodyAsync(cancellationToken).ConfigureAwait(false); // Wait for response headers to be read. await http2Stream.ReadResponseHeadersAsync().ConfigureAwait(false); @@ -1320,6 +1369,21 @@ public sealed override async Task SendAsync(HttpRequestMess // ISSUE 31315: Determine if/how to expose HTTP2 error codes throw new HttpRequestException(SR.net_http_client_execution_error, e); } + else if (e is OperationCanceledException oce) + { + // If the operation has been canceled after the stream was allocated an ID, send a RST_STREAM. + if (http2Stream != null && http2Stream.StreamId != 0) + { + http2Stream.Cancel(); + } + + if (oce.CancellationToken == cancellationToken) + { + throw; + } + + throw new OperationCanceledException(cancellationToken); + } else { throw; diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index a93acb9e5e2b..3c9ad5ee0214 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.IO; using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -88,7 +87,7 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection, int s public HttpRequestMessage Request => _request; public HttpResponseMessage Response => _response; - public async Task SendRequestBodyAsync() + public async Task SendRequestBodyAsync(CancellationToken cancellationToken) { // TODO: ISSUE 31312: Expect: 100-continue and early response handling // Note that in an "early response" scenario, where we get a response before we've finished sending the request body @@ -100,8 +99,11 @@ public async Task SendRequestBodyAsync() { using (Http2WriteStream writeStream = new Http2WriteStream(this)) { - await _request.Content.CopyToAsync(writeStream).ConfigureAwait(false); + await _request.Content.CopyToAsync(writeStream, null, cancellationToken).ConfigureAwait(false); } + + // Don't wait for completion, which could happen asynchronously. + Task ignored = _connection.SendEndStreamAsync(_streamId); } } @@ -364,7 +366,7 @@ private void ExtendWindow(int amount) int windowUpdateSize = _pendingWindowUpdate; _pendingWindowUpdate = 0; - ValueTask ignored = _connection.SendWindowUpdateAsync(_streamId, windowUpdateSize); + Task ignored = _connection.SendWindowUpdateAsync(_streamId, windowUpdateSize); } private (bool wait, int bytesRead) TryReadFromBuffer(Span buffer) @@ -404,6 +406,32 @@ private void ExtendWindow(int amount) } } + public int ReadData(Span buffer) + { + if (buffer.Length == 0) + { + return 0; + } + + (bool wait, int bytesRead) = TryReadFromBuffer(buffer); + if (wait) + { + // Synchronously block waiting for data to be produced. + Debug.Assert(bytesRead == 0); + GetWaiterTask().AsTask().GetAwaiter().GetResult(); + (wait, bytesRead) = TryReadFromBuffer(buffer); + Debug.Assert(!wait); + } + + if (bytesRead != 0) + { + ExtendWindow(bytesRead); + _connection.ExtendWindow(bytesRead); + } + + return bytesRead; + } + public async ValueTask ReadDataAsync(Memory buffer, CancellationToken cancellationToken) { if (buffer.Length == 0) @@ -429,18 +457,18 @@ public async ValueTask ReadDataAsync(Memory buffer, CancellationToken return bytesRead; } - private async ValueTask SendDataAsync(ReadOnlyMemory buffer) + private async Task SendDataAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { ReadOnlyMemory remaining = buffer; while (remaining.Length > 0) { - int sendSize = await _streamWindow.RequestCreditAsync(remaining.Length).ConfigureAwait(false); + int sendSize = await _streamWindow.RequestCreditAsync(remaining.Length, cancellationToken).ConfigureAwait(false); ReadOnlyMemory current; (current, remaining) = SplitBuffer(remaining, sendSize); - await _connection.SendStreamDataAsync(_streamId, current).ConfigureAwait(false); + await _connection.SendStreamDataAsync(_streamId, current, cancellationToken).ConfigureAwait(false); } } @@ -460,6 +488,25 @@ public void Dispose() } } + public void Cancel() + { + bool signalWaiter; + lock (SyncObject) + { + Task ignored = _connection.SendRstStreamAsync(_streamId, Http2ProtocolErrorCode.Cancel); + _state = StreamState.Aborted; + + signalWaiter = _hasWaiter; + _hasWaiter = false; + } + if (signalWaiter) + { + _waitSource.SetResult(true); + } + + _connection.RemoveStream(this); + } + // This object is itself usable as a backing source for ValueTask. Since there's only ever one awaiter // for this object's state transitions at a time, we allow the object to be awaited directly. All functionality // associated with the implementation is just delegated to the ManualResetValueTaskSourceCore. @@ -468,7 +515,7 @@ public void Dispose() void IValueTaskSource.OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _waitSource.OnCompleted(continuation, state, token, flags); void IValueTaskSource.GetResult(short token) => _waitSource.GetResult(token); - private sealed class Http2ReadStream : BaseAsyncStream + private sealed class Http2ReadStream : HttpBaseStream { private Http2Stream _http2Stream; @@ -497,6 +544,12 @@ protected override void Dispose(bool disposing) public override bool CanRead => true; public override bool CanWrite => false; + public override int Read(Span destination) + { + Http2Stream http2Stream = _http2Stream ?? throw new ObjectDisposedException(nameof(Http2ReadStream)); + return http2Stream.ReadData(destination); + } + public override ValueTask ReadAsync(Memory destination, CancellationToken cancellationToken) { Http2Stream http2Stream = _http2Stream; @@ -508,13 +561,12 @@ public override ValueTask ReadAsync(Memory destination, CancellationT return http2Stream.ReadDataAsync(destination, cancellationToken); } - public override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); + public override void Write(ReadOnlySpan buffer) => throw new NotSupportedException(SR.net_http_content_readonly_stream); - public override Task FlushAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); + public override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); } - - private sealed class Http2WriteStream : BaseAsyncStream + private sealed class Http2WriteStream : HttpBaseStream { private Http2Stream _http2Stream; @@ -532,15 +584,14 @@ protected override void Dispose(bool disposing) return; } - // Don't wait for completion, which could happen asynchronously. - ValueTask ignored = http2Stream._connection.SendEndStreamAsync(http2Stream.StreamId); - base.Dispose(disposing); } public override bool CanRead => false; public override bool CanWrite => true; + public override int Read(Span buffer) => throw new NotSupportedException(); + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => throw new NotSupportedException(); public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) @@ -551,10 +602,8 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo return new ValueTask(Task.FromException(new ObjectDisposedException(nameof(Http2WriteStream)))); } - return http2Stream.SendDataAsync(buffer); + return new ValueTask(http2Stream.SendDataAsync(buffer, cancellationToken)); } - - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; } } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/BaseAsyncStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpBaseStream.cs similarity index 75% rename from src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/BaseAsyncStream.cs rename to src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpBaseStream.cs index fed81030135f..b9b2c42a63f2 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/BaseAsyncStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpBaseStream.cs @@ -3,12 +3,13 @@ // See the LICENSE file in the project root for more information. using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; namespace System.Net.Http { - internal abstract class BaseAsyncStream : Stream + internal abstract class HttpBaseStream : Stream { public sealed override bool CanSeek => false; @@ -78,10 +79,16 @@ protected static void ValidateCopyToArgs(Stream source, Stream destination, int } } + public sealed override int ReadByte() + { + byte b = 0; + return Read(MemoryMarshal.CreateSpan(ref b, 1)) == 1 ? b : -1; + } + public sealed override int Read(byte[] buffer, int offset, int count) { ValidateBufferArgs(buffer, offset, count); - return ReadAsync(new Memory(buffer, offset, count), CancellationToken.None).GetAwaiter().GetResult(); + return Read(buffer.AsSpan(offset, count)); } public sealed override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) @@ -90,30 +97,39 @@ public sealed override Task ReadAsync(byte[] buffer, int offset, int count, return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); } - public sealed override void Write(byte[] buffer, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) { + // This does sync-over-async, but it also should only end up being used in strange + // situations. Either a derived stream overrides this anyway, so the implementation won't be used, + // or it's being called as part of HttpContent.SerializeToStreamAsync, which means custom + // content is explicitly choosing to make a synchronous call as part of an asynchronous method. ValidateBufferArgs(buffer, offset, count); WriteAsync(new Memory(buffer, offset, count), CancellationToken.None).GetAwaiter().GetResult(); } + public sealed override void WriteByte(byte value) => + Write(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + public sealed override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { ValidateBufferArgs(buffer, offset, count); return WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); } - public sealed override void Flush() => FlushAsync().GetAwaiter().GetResult(); + public override void Flush() { } - public sealed override void CopyTo(Stream destination, int bufferSize) => - CopyToAsync(destination, bufferSize, CancellationToken.None).GetAwaiter().GetResult(); + public override Task FlushAsync(CancellationToken cancellationToken) => NopAsync(cancellationToken); + protected static Task NopAsync(CancellationToken cancellationToken) => + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : + Task.CompletedTask; // // Methods which must be implemented by derived classes // + public abstract override int Read(Span buffer); public abstract override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken); - public abstract override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken); - public abstract override Task FlushAsync(CancellationToken cancellationToken); + public abstract override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs index c27762fe7b92..ff10f69221d1 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs @@ -972,6 +972,13 @@ private static void ParseHeaderNameValue(HttpConnection connection, ReadOnlySpan private static bool IsDigit(byte c) => (uint)(c - '0') <= '9' - '0'; + private void WriteToBuffer(ReadOnlySpan source) + { + Debug.Assert(source.Length <= _writeBuffer.Length - _writeOffset); + source.CopyTo(new Span(_writeBuffer, _writeOffset, source.Length)); + _writeOffset += source.Length; + } + private void WriteToBuffer(ReadOnlyMemory source) { Debug.Assert(source.Length <= _writeBuffer.Length - _writeOffset); @@ -1010,6 +1017,30 @@ private async Task WriteAsync(ReadOnlyMemory source) } } + private void WriteWithoutBuffering(ReadOnlySpan source) + { + if (_writeOffset != 0) + { + int remaining = _writeBuffer.Length - _writeOffset; + if (source.Length <= remaining) + { + // There's something already in the write buffer, but the content + // we're writing can also fit after it in the write buffer. Copy + // the content to the write buffer and then flush it, so that we + // can do a single send rather than two. + WriteToBuffer(source); + Flush(); + return; + } + + // There's data in the write buffer and the data we're writing doesn't fit after it. + // Do two writes, one to flush the buffer and then another to write the supplied content. + Flush(); + } + + WriteToStream(source); + } + private ValueTask WriteWithoutBufferingAsync(ReadOnlyMemory source) { if (_writeOffset == 0) @@ -1172,6 +1203,15 @@ private async Task WriteStringAsyncSlow(string s) } } + private void Flush() + { + if (_writeOffset > 0) + { + WriteToStream(new ReadOnlySpan(_writeBuffer, 0, _writeOffset)); + _writeOffset = 0; + } + } + private ValueTask FlushAsync() { if (_writeOffset > 0) @@ -1183,6 +1223,12 @@ private ValueTask FlushAsync() return default; } + private void WriteToStream(ReadOnlySpan source) + { + if (NetEventSource.IsEnabled) Trace($"Writing {source.Length} bytes."); + _stream.Write(source); + } + private ValueTask WriteToStreamAsync(ReadOnlyMemory source) { if (NetEventSource.IsEnabled) Trace($"Writing {source.Length} bytes."); @@ -1312,6 +1358,52 @@ private void ThrowIfExceededAllowedReadLineBytes() } } + // Throws IOException on EOF. This is only called when we expect more data. + private void Fill() + { + Debug.Assert(_readAheadTask == null); + + int remaining = _readLength - _readOffset; + Debug.Assert(remaining >= 0); + + if (remaining == 0) + { + // No data in the buffer. Simply reset the offset and length to 0 to allow + // the whole buffer to be filled. + _readOffset = _readLength = 0; + } + else if (_readOffset > 0) + { + // There's some data in the buffer but it's not at the beginning. Shift it + // down to make room for more. + Buffer.BlockCopy(_readBuffer, _readOffset, _readBuffer, 0, remaining); + _readOffset = 0; + _readLength = remaining; + } + else if (remaining == _readBuffer.Length) + { + // The whole buffer is full, but the caller is still requesting more data, + // so increase the size of the buffer. + Debug.Assert(_readOffset == 0); + Debug.Assert(_readLength == _readBuffer.Length); + + var newReadBuffer = new byte[_readBuffer.Length * 2]; + Buffer.BlockCopy(_readBuffer, 0, newReadBuffer, 0, remaining); + _readBuffer = newReadBuffer; + _readOffset = 0; + _readLength = remaining; + } + + int bytesRead = _stream.Read(_readBuffer, _readLength, _readBuffer.Length - _readLength); + if (NetEventSource.IsEnabled) Trace($"Received {bytesRead} bytes."); + if (bytesRead == 0) + { + throw new IOException(SR.net_http_invalid_response); + } + + _readLength += bytesRead; + } + // Throws IOException on EOF. This is only called when we expect more data. private async Task FillAsync() { @@ -1341,7 +1433,7 @@ private async Task FillAsync() Debug.Assert(_readOffset == 0); Debug.Assert(_readLength == _readBuffer.Length); - byte[] newReadBuffer = new byte[_readBuffer.Length * 2]; + var newReadBuffer = new byte[_readBuffer.Length * 2]; Buffer.BlockCopy(_readBuffer, 0, newReadBuffer, 0, remaining); _readBuffer = newReadBuffer; _readOffset = 0; @@ -1367,6 +1459,34 @@ private void ReadFromBuffer(Span buffer) _readOffset += buffer.Length; } + private int Read(Span destination) + { + // This is called when reading the response body. + + int remaining = _readLength - _readOffset; + if (remaining > 0) + { + // We have data in the read buffer. Return it to the caller. + if (destination.Length <= remaining) + { + ReadFromBuffer(destination); + return destination.Length; + } + else + { + ReadFromBuffer(destination.Slice(0, remaining)); + return remaining; + } + } + + // No data in read buffer. + // Do an unbuffered read directly against the underlying stream. + Debug.Assert(_readAheadTask == null, "Read ahead task should have been consumed as part of the headers."); + int count = _stream.Read(destination); + if (NetEventSource.IsEnabled) Trace($"Received {count} bytes."); + return count; + } + private async ValueTask ReadAsync(Memory destination) { // This is called when reading the response body. @@ -1395,6 +1515,43 @@ private async ValueTask ReadAsync(Memory destination) return count; } + private int ReadBuffered(Span destination) + { + // This is called when reading the response body. + Debug.Assert(destination.Length != 0); + + int remaining = _readLength - _readOffset; + if (remaining > 0) + { + // We have data in the read buffer. Return it to the caller. + if (destination.Length <= remaining) + { + ReadFromBuffer(destination); + return destination.Length; + } + else + { + ReadFromBuffer(destination.Slice(0, remaining)); + return remaining; + } + } + + // No data in read buffer. + _readOffset = _readLength = 0; + + // Do a buffered read directly against the underlying stream. + Debug.Assert(_readAheadTask == null, "Read ahead task should have been consumed as part of the headers."); + int bytesRead = _stream.Read(_readBuffer, 0, _readBuffer.Length); + if (NetEventSource.IsEnabled) Trace($"Received {bytesRead} bytes."); + _readLength = bytesRead; + + // Hand back as much data as we can fit. + int bytesToCopy = Math.Min(bytesRead, destination.Length); + _readBuffer.AsSpan(0, bytesToCopy).CopyTo(destination); + _readOffset = bytesToCopy; + return bytesToCopy; + } + private ValueTask ReadBufferedAsync(Memory destination) { // If the caller provided buffer, and thus the amount of data desired to be read, diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index f25c0cde5ab6..5b95496fe47e 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -28,7 +28,7 @@ internal sealed class HttpConnectionPool : IDisposable private readonly int _port; private readonly Uri _proxyUri; internal readonly byte[] _encodedAuthorityHostHeader; - + /// List of idle connections stored in the pool. private readonly List _idleConnections = new List(); /// The maximum number of connections allowed to be associated with the pool. @@ -77,8 +77,7 @@ public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionK Debug.Assert(port != 0); Debug.Assert(sslHostName == null); Debug.Assert(proxyUri == null); - - _http2Enabled = false; + _http2Enabled = _poolManager.Settings._allowUnencryptedHttp2; break; case HttpConnectionKind.Https: @@ -140,6 +139,10 @@ public HttpConnectionPool(HttpConnectionPoolManager poolManager, HttpConnectionK // Note the IDN hostname should always be ASCII, since it's already been IDNA encoded. _hostHeaderValueBytes = Encoding.ASCII.GetBytes(hostHeader); Debug.Assert(Encoding.ASCII.GetString(_hostHeaderValueBytes) == hostHeader); + if (sslHostName == null) + { + _encodedAuthorityHostHeader = HPackEncoder.EncodeLiteralHeaderFieldWithoutIndexingToAllocatedArray(StaticTable.Authority, hostHeader); + } } if (sslHostName != null) @@ -328,7 +331,7 @@ private ValueTask GetOrReserveHttp11ConnectionAsync(Cancellation private async ValueTask<(HttpConnectionBase connection, bool isNewConnection, HttpResponseMessage failureResponse)> GetHttp2ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel); + Debug.Assert(_kind == HttpConnectionKind.Https || _kind == HttpConnectionKind.SslProxyTunnel || _kind == HttpConnectionKind.Http); // See if we have an HTTP2 connection Http2Connection http2Connection = _http2Connection; @@ -401,6 +404,22 @@ private ValueTask GetOrReserveHttp11ConnectionAsync(Cancellation return (null, true, failureResponse); } + if (_kind == HttpConnectionKind.Http) + { + http2Connection = new Http2Connection(this, stream); + await http2Connection.SetupAsync().ConfigureAwait(false); + + Debug.Assert(_http2Connection == null); + _http2Connection = http2Connection; + + if (NetEventSource.IsEnabled) + { + Trace("New unencrypted HTTP2 connection established."); + } + + return (_http2Connection, true, null); + } + sslStream = (SslStream)stream; if (sslStream.NegotiatedApplicationProtocol == SslApplicationProtocol.Http2) { @@ -1071,28 +1090,5 @@ public bool IsUsable( public override bool Equals(object obj) => obj is CachedConnection && Equals((CachedConnection)obj); public override int GetHashCode() => _connection?.GetHashCode() ?? 0; } - - private sealed class TaskCompletionSourceWithCancellation : TaskCompletionSource - { - private CancellationToken _cancellationToken; - - public TaskCompletionSourceWithCancellation() : base(TaskCreationOptions.RunContinuationsAsynchronously) - { - } - - private void OnCancellation() - { - TrySetCanceled(_cancellationToken); - } - - public async Task WaitWithCancellationAsync(CancellationToken cancellationToken) - { - _cancellationToken = cancellationToken; - using (cancellationToken.Register(s => ((TaskCompletionSourceWithCancellation)s).OnCancellation(), this)) - { - return await Task.ConfigureAwait(false); - } - } - } } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs index 5e3f677b67b7..a8cb6249e620 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionSettings.cs @@ -12,7 +12,9 @@ internal sealed class HttpConnectionSettings { private const string Http2SupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2SUPPORT"; private const string Http2SupportAppCtxSettingName = "System.Net.Http.SocketsHttpHandler.Http2Support"; - + private const string Http2UnencryptedSupportEnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_HTTP2UNENCRYPTEDSUPPORT"; + private const string Http2UnencryptedSupportAppCtxSettingName = "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport"; + internal DecompressionMethods _automaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression; internal bool _useCookies = HttpHandlerDefaults.DefaultUseCookies; @@ -40,13 +42,17 @@ internal sealed class HttpConnectionSettings internal Version _maxHttpVersion; + internal bool _allowUnencryptedHttp2; + internal SslClientAuthenticationOptions _sslOptions; internal IDictionary _properties; public HttpConnectionSettings() { - _maxHttpVersion = AllowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11; + bool allowHttp2 = AllowHttp2; + _maxHttpVersion = allowHttp2 ? HttpVersion.Version20 : HttpVersion.Version11; + _allowUnencryptedHttp2 = allowHttp2 && AllowUnencryptedHttp2; } /// Creates a copy of the settings but with some values normalized to suit the implementation. @@ -92,6 +98,7 @@ public HttpConnectionSettings CloneAndNormalize() _sslOptions = _sslOptions?.ShallowClone(), // shallow clone the options for basic prevention of mutation issues while processing _useCookies = _useCookies, _useProxy = _useProxy, + _allowUnencryptedHttp2 = _allowUnencryptedHttp2, }; } @@ -117,5 +124,28 @@ private static bool AllowHttp2 return false; } } + + private static bool AllowUnencryptedHttp2 + { + get + { + // First check for the AppContext switch, giving it priority over the environment variable. + if (AppContext.TryGetSwitch(Http2UnencryptedSupportAppCtxSettingName, out bool allowHttp2)) + { + return allowHttp2; + } + + // AppContext switch wasn't used. Check the environment variable. + string envVar = Environment.GetEnvironmentVariable(Http2UnencryptedSupportEnvironmentVariableSettingName); + if (envVar != null && (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1"))) + { + // Allow HTTP/2.0 protocol for HTTP endpoints. + return true; + } + + // Default to a maximum of HTTP/1.1. + return false; + } + } } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs index 080ff78117e9..b7527bede787 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentReadStream.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -22,9 +21,9 @@ public HttpContentReadStream(HttpConnection connection) : base(connection) public sealed override bool CanRead => true; public sealed override bool CanWrite => false; - public sealed override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); + public sealed override void Write(ReadOnlySpan buffer) => throw new NotSupportedException(SR.net_http_content_readonly_stream); - public override Task FlushAsync(CancellationToken cancellationToken) => throw new NotSupportedException(); + public sealed override ValueTask WriteAsync(ReadOnlyMemory destination, CancellationToken cancellationToken) => throw new NotSupportedException(); public virtual bool NeedsDrain => false; diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs index f3a0f0144f61..b1b40ea960d9 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentStream.cs @@ -4,7 +4,7 @@ namespace System.Net.Http { - internal abstract class HttpContentStream : BaseAsyncStream + internal abstract class HttpContentStream : HttpBaseStream { protected HttpConnection _connection; diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs index 9dd513fcb7db..6802a07265c3 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpContentWriteStream.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; @@ -18,12 +19,19 @@ public HttpContentWriteStream(HttpConnection connection) : base(connection) => public sealed override bool CanRead => false; public sealed override bool CanWrite => true; + public sealed override void Flush() => + _connection.Flush(); + public sealed override Task FlushAsync(CancellationToken ignored) => _connection.FlushAsync().AsTask(); - public abstract Task FinishAsync(); + public sealed override int Read(Span buffer) => throw new NotSupportedException(); public sealed override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) => throw new NotSupportedException(); + + public sealed override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => throw new NotSupportedException(); + + public abstract Task FinishAsync(); } } } diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs new file mode 100644 index 000000000000..dfd5b31bbd87 --- /dev/null +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/MacProxy.cs @@ -0,0 +1,128 @@ +// 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.Net.Http; +using System.Net; +using System.Collections.Generic; +using Microsoft.Win32.SafeHandles; +using static Interop.CoreFoundation; +using static Interop.RunLoop; + +using CFRunLoopRef = System.IntPtr; +using CFRunLoopSourceRef = System.IntPtr; + +namespace System.Net.Http +{ + internal sealed class MacProxy : IWebProxy + { + public ICredentials Credentials + { + get => null; + set => throw new NotSupportedException(); + } + + private static Uri GetProxyUri(string scheme, CFProxy proxy) + { + var uriBuilder = new UriBuilder( + scheme, + proxy.HostName, + proxy.PortNumber); + + // TODO: Issue #26593 - Credentials are not propagated + + return uriBuilder.Uri; + } + + public Uri ExecuteProxyAutoConfiguration(SafeCreateHandle cfurl, CFProxy proxy) + { + Uri result = null; + CFRunLoopRef runLoop = CFRunLoopGetCurrent(); + + // Callback that will be called after executing the configuration script + CFProxyAutoConfigurationResultCallback cb = (IntPtr client, IntPtr proxyListHandle, IntPtr error) => + { + if (proxyListHandle != IntPtr.Zero) + { + using (var proxyList = new SafeCFArrayHandle(proxyListHandle, false)) + { + long proxyCount = CFArrayGetCount(proxyList); + for (int i = 0; i < proxyCount; i++) + { + IntPtr proxyValue = CFArrayGetValueAtIndex(proxyList, i); + using (SafeCFDictionaryHandle proxyDict = new SafeCFDictionaryHandle(proxyValue, false)) + { + CFProxy proxy = new CFProxy(proxyDict); + if (proxy.ProxyType == CFProxy.kCFProxyTypeHTTP || proxy.ProxyType == CFProxy.kCFProxyTypeHTTPS) + { + result = GetProxyUri("http", proxy); + break; + } + } + } + } + } + CFRunLoopStop(runLoop); + }; + + var clientContext = new CFStreamClientContext(); + CFRunLoopSourceRef loopSource = + proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationURL ? + CFNetworkExecuteProxyAutoConfigurationURL(proxy.AutoConfigurationURL, cfurl, cb, ref clientContext) : + CFNetworkExecuteProxyAutoConfigurationScript(proxy.AutoConfigurationJavaScript, cfurl, cb, ref clientContext); + + using (var mode = CFStringCreateWithCString(typeof(MacProxy).FullName)) + { + IntPtr modeHandle = mode.DangerousGetHandle(); + CFRunLoopAddSource(runLoop, loopSource, modeHandle); + CFRunLoopRunInMode(modeHandle, double.MaxValue, 0); + CFRunLoopSourceInvalidate(loopSource); + } + + GC.KeepAlive(cb); + + return result; + } + + public Uri GetProxy(Uri targetUri) + { + using (SafeCFDictionaryHandle systemProxySettings = CFNetworkCopySystemProxySettings()) + using (SafeCreateHandle cfurl = CFURLCreateWithString(targetUri.AbsoluteUri)) + using (SafeCFArrayHandle proxies = CFNetworkCopyProxiesForURL(cfurl, systemProxySettings)) + { + long proxyCount = CFArrayGetCount(proxies); + for (int i = 0; i < proxyCount; i++) + { + IntPtr proxyValue = CFArrayGetValueAtIndex(proxies, i); + using (SafeCFDictionaryHandle proxyDict = new SafeCFDictionaryHandle(proxyValue, false)) + { + CFProxy proxy = new CFProxy(proxyDict); + + if (proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationURL || proxy.ProxyType == CFProxy.kCFProxyTypeAutoConfigurationJavaScript) + { + Uri result = ExecuteProxyAutoConfiguration(cfurl, proxy); + if (result != null) + return result; + } + else if (proxy.ProxyType == CFProxy.kCFProxyTypeHTTP || proxy.ProxyType == CFProxy.kCFProxyTypeHTTPS) + { + return GetProxyUri("http", proxy); + } + } + } + } + + return null; + } + + public bool IsBypassed(Uri targetUri) + { + if (targetUri == null) + throw new ArgumentNullException(nameof(targetUri)); + + Uri proxyUri = GetProxy(targetUri); + return Equals(proxyUri, targetUri) || proxyUri == null; + } + } +} diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs index 52acc7fa07c1..81d1dfc1b6c0 100644 --- a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RawConnectionStream.cs @@ -20,6 +20,25 @@ public RawConnectionStream(HttpConnection connection) : base(connection) public sealed override bool CanRead => true; public sealed override bool CanWrite => true; + public override int Read(Span buffer) + { + if (_connection == null || buffer.Length == 0) + { + // Response body fully consumed or the caller didn't ask for any data + return 0; + } + + int bytesRead = _connection.ReadBuffered(buffer); + if (bytesRead == 0) + { + // We cannot reuse this connection, so close it. + _connection.Dispose(); + _connection = null; + } + + return bytesRead; + } + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken) { CancellationHelper.ThrowIfCancellationRequested(cancellationToken); @@ -61,7 +80,6 @@ public override async ValueTask ReadAsync(Memory buffer, Cancellation // We cannot reuse this connection, so close it. _connection.Dispose(); _connection = null; - return 0; } return bytesRead; @@ -124,6 +142,25 @@ private void Finish() _connection = null; } + public override void Write(byte[] buffer, int offset, int count) + { + ValidateBufferArgs(buffer, offset, count); + Write(buffer.AsSpan(offset, count)); + } + + public override void Write(ReadOnlySpan buffer) + { + if (_connection == null) + { + throw new IOException(SR.net_http_io_write); + } + + if (buffer.Length != 0) + { + _connection.WriteWithoutBuffering(buffer); + } + } + public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -147,6 +184,8 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo new ValueTask(WaitWithConnectionCancellationAsync(writeTask, cancellationToken)); } + public override void Flush() => _connection?.Flush(); + public override Task FlushAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.cs new file mode 100644 index 000000000000..2d143dd0183d --- /dev/null +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.OSX.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. +// See the LICENSE file in the project root for more information. + +namespace System.Net.Http +{ + internal static class SystemProxyInfo + { + // On Unix we get default proxy configuration from environment variables + public static IWebProxy ConstructSystemProxy() + { + return HttpEnvironmentProxy.TryCreate(out IWebProxy proxy) ? proxy : new MacProxy(); + } + } +} + diff --git a/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/TaskCompletionSourceWithCancellation.cs b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/TaskCompletionSourceWithCancellation.cs new file mode 100644 index 000000000000..d462b163590a --- /dev/null +++ b/src/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/TaskCompletionSourceWithCancellation.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; + +namespace System.Net.Http +{ + internal sealed class TaskCompletionSourceWithCancellation : TaskCompletionSource + { + private CancellationToken _cancellationToken; + + public TaskCompletionSourceWithCancellation() : base(TaskCreationOptions.RunContinuationsAsynchronously) + { + } + + private void OnCancellation() + { + TrySetCanceled(_cancellationToken); + } + + public async Task WaitWithCancellationAsync(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + using (cancellationToken.UnsafeRegister(s => ((TaskCompletionSourceWithCancellation)s).OnCancellation(), this)) + { + return await Task.ConfigureAwait(false); + } + } + } +} diff --git a/src/System.Net.Http/src/uap/System/Net/cookie.cs b/src/System.Net.Http/src/uap/System/Net/cookie.cs index 5fe6ce5dd207..250e47a06316 100644 --- a/src/System.Net.Http/src/uap/System/Net/cookie.cs +++ b/src/System.Net.Http/src/uap/System/Net/cookie.cs @@ -65,7 +65,8 @@ public sealed class Cookie internal const string SpecialAttributeLiteral = "$"; internal static readonly char[] PortSplitDelimiters = new char[] { ' ', ',', '\"' }; - internal static readonly char[] Reserved2Name = new char[] { ' ', '\t', '\r', '\n', '=', ';', ',' }; + // Space (' ') should be reserved as well per RFCs, but major web browsers support it and some web sites use it - so we support it too + internal static readonly char[] Reserved2Name = new char[] { '\t', '\r', '\n', '=', ';', ',' }; internal static readonly char[] Reserved2Value = new char[] { ';', ',' }; // fields @@ -286,7 +287,7 @@ public string Name internal bool InternalSetName(string value) { - if (string.IsNullOrEmpty(value) || value[0] == '$' || value.IndexOfAny(Reserved2Name) != -1) + if (string.IsNullOrEmpty(value) || value[0] == '$' || value.IndexOfAny(Reserved2Name) != -1 || value[0] == ' ' || value[value.Length - 1] == ' ') { m_name = string.Empty; return false; @@ -399,7 +400,7 @@ internal bool VerifySetDefaults(CookieVariant variant, Uri uri, bool isLocalDoma } //Check the name - if (m_name == null || m_name.Length == 0 || m_name[0] == '$' || m_name.IndexOfAny(Reserved2Name) != -1) + if (string.IsNullOrEmpty(m_name) || m_name[0] == '$' || m_name.IndexOfAny(Reserved2Name) != -1 || m_name[0] == ' ' || m_name[m_name.Length - 1] == ' ') { if (isThrow) { diff --git a/src/System.Net.Http/tests/FunctionalTests/DefaultCredentialsTest.cs b/src/System.Net.Http/tests/FunctionalTests/DefaultCredentialsTest.cs index db011f194db2..3b219069ff8d 100644 --- a/src/System.Net.Http/tests/FunctionalTests/DefaultCredentialsTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/DefaultCredentialsTest.cs @@ -36,12 +36,7 @@ public abstract class DefaultCredentialsTest : HttpClientHandlerTestBase private static Uri s_authenticatedServer = DomainJoinedTestsEnabled ? new Uri($"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx") : null; - private readonly ITestOutputHelper _output; - - public DefaultCredentialsTest(ITestOutputHelper output) - { - _output = output; - } + public DefaultCredentialsTest(ITestOutputHelper output) : base(output) { } [OuterLoop("Uses external server")] [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))] diff --git a/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs b/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs index 2b07e4169a5a..dc8e57b09bbd 100644 --- a/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/DiagnosticsTests.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -23,6 +24,8 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "NetEventSource is only part of .NET Core.")] public abstract class DiagnosticsTest : HttpClientHandlerTestBase { + public DiagnosticsTest(ITestOutputHelper output) : base(output) { } + [Fact] public static void EventSource_ExistsWithCorrectId() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClient.SelectedSitesTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClient.SelectedSitesTest.cs index d6decc2955bd..8009eb7ecb56 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClient.SelectedSitesTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClient.SelectedSitesTest.cs @@ -8,11 +8,14 @@ using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { public abstract class HttpClient_SelectedSites_Test : HttpClientHandlerTestBase { + public HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { } + public static bool IsSelectedSitesTestEnabled() { string envVar = Environment.GetEnvironmentVariable("CORFX_NET_HTTP_SELECTED_SITES"); diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs index e2a6cc8f7b32..81d300211cca 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientEKUTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -44,6 +45,8 @@ public abstract class HttpClientEKUTest : HttpClientHandlerTestBase private VerboseTestLogging _log = VerboseTestLogging.GetInstance(); + public HttpClientEKUTest(ITestOutputHelper output) : base(output) { } + [ConditionalFact(nameof(CanTestCertificates))] public async Task HttpClient_NoEKUServerAuth_Ok() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AcceptAllCerts.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AcceptAllCerts.cs index 3fd307c2553a..25e001635b98 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AcceptAllCerts.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AcceptAllCerts.cs @@ -7,6 +7,7 @@ using System.Security.Authentication; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -16,6 +17,8 @@ public abstract class HttpClientHandler_DangerousAcceptAllCertificatesValidator_ { private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater); + public HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { } + [Fact] public void SingletonReturnsTrue() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Asynchrony.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Asynchrony.cs index ba18160bc42a..2239e4942f91 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Asynchrony.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Asynchrony.cs @@ -10,11 +10,14 @@ using System.Threading.Tasks; using System.Threading.Tests; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandler_Asynchrony_Test : HttpClientHandlerTestBase { + public HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { } + public static IEnumerable ResponseHeadersRead_SynchronizationContextNotUsedByHandler_MemberData() => from responseHeadersRead in new[] { false, true } from contentMode in Enum.GetValues(typeof(LoopbackServer.ContentMode)).Cast() diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs index a67331bfbacc..dd882fb5d6a3 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Authentication.cs @@ -19,8 +19,6 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Tests would need to be rewritten due to behavior differences with WinRT")] public abstract class HttpClientHandler_Authentication_Test : HttpClientHandlerTestBase { - private readonly ITestOutputHelper _output; - private const string Username = "testusername"; private const string Password = "testpassword"; private const string Domain = "testdomain"; @@ -39,10 +37,7 @@ public abstract class HttpClientHandler_Authentication_Test : HttpClientHandlerT } }; - public HttpClientHandler_Authentication_Test(ITestOutputHelper output) - { - _output = output; - } + public HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { } [Theory] [MemberData(nameof(Authentication_TestData))] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AutoRedirect.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AutoRedirect.cs index 8cc5429e5120..9a3fb6f7a883 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AutoRedirect.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.AutoRedirect.cs @@ -17,7 +17,6 @@ namespace System.Net.Http.Functional.Tests public abstract class HttpClientHandlerTest_AutoRedirect : HttpClientHandlerTestBase { - readonly ITestOutputHelper _output; private const string ExpectedContent = "Test content"; private const string Username = "testuser"; private const string Password = "password"; @@ -61,10 +60,7 @@ public abstract class HttpClientHandlerTest_AutoRedirect : HttpClientHandlerTest new object[] { 308, "HEAD", "HEAD" }, }; - public HttpClientHandlerTest_AutoRedirect(ITestOutputHelper output) - { - _output = output; - } + public HttpClientHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { } [OuterLoop("Uses external server")] [Theory, MemberData(nameof(RedirectStatusCodes))] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cancellation.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cancellation.cs index 9c6e64cebbe9..9c061ade7e92 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cancellation.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cancellation.cs @@ -10,11 +10,14 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandler_Cancellation_Test : HttpClientHandlerTestBase { + public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(false, CancellationMode.Token)] [InlineData(true, CancellationMode.Token)] @@ -343,6 +346,46 @@ await LoopbackServer.CreateServerAsync(async (server, url) => } } + [Fact] + public async Task SendAsync_Cancel_CancellationTokenPropagates() + { + TaskCompletionSource clientCanceled = new TaskCompletionSource(); + await LoopbackServerFactory.CreateClientAndServerAsync( + async uri => + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + + using (HttpClient client = CreateHttpClient()) + { + OperationCanceledException ex = null; + try + { + await client.GetAsync(uri, cts.Token); + } + catch(OperationCanceledException e) + { + ex = e; + } + Assert.True(ex != null, "Expected OperationCancelledException, but no exception was thrown."); + + Assert.True(cts.Token.IsCancellationRequested, "cts token IsCancellationRequested"); + + if (!PlatformDetection.IsFullFramework) + { + // .NET Framework has bug where it doesn't propagate token information. + Assert.True(ex.CancellationToken.IsCancellationRequested, "exception token IsCancellationRequested"); + } + clientCanceled.SetResult(true); + } + }, + async server => + { + Task serverTask = server.HandleRequestAsync(); + await clientCanceled.Task; + }); + } + private async Task ValidateClientCancellationAsync(Func clientBodyAsync) { var stopwatch = Stopwatch.StartNew(); diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs index 312d8bd88466..5d888af59540 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ClientCertificates.cs @@ -26,12 +26,8 @@ public abstract class HttpClientHandler_ClientCertificates_Test : HttpClientHand public bool CanTestClientCertificates => CanTestCertificates && BackendSupportsCustomCertificateHandling; - public HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) - { - _output = output; - } + public HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { } - private readonly ITestOutputHelper _output; [Fact] public void ClientCertificateOptions_Default() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cookies.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cookies.cs index e471ab7f9807..060a5ef3f831 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cookies.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Cookies.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -22,6 +23,8 @@ public abstract class HttpClientHandlerTest_Cookies : HttpClientHandlerTestBase private const string s_simpleContent = "Hello world!"; + public HttpClientHandlerTest_Cookies(ITestOutputHelper output) : base(output) { } + // // Send cookie tests // @@ -628,6 +631,10 @@ public static IEnumerable CookieNamesValuesAndUseCookies() yield return new object[] { "Hello", "World", useCookies }; yield return new object[] { "foo", "bar", useCookies }; + if (!PlatformDetection.IsFullFramework) { + yield return new object[] { "Hello World", "value", useCookies }; + } + yield return new object[] { ".AspNetCore.Session", "RAExEmXpoCbueP_QYM", useCookies }; yield return new object[] @@ -666,6 +673,8 @@ public static IEnumerable CookieNamesValuesAndUseCookies() public abstract class HttpClientHandlerTest_Cookies_Http11 : HttpClientHandlerTestBase { + public HttpClientHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { } + [Fact] public async Task GetAsync_ReceiveMultipleSetCookieHeaders_CookieAdded() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Decompression.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Decompression.cs index 8af2fdb9bcae..dd1a284b8f36 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Decompression.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Decompression.cs @@ -16,14 +16,9 @@ namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandler_Decompression_Test : HttpClientHandlerTestBase { - private readonly ITestOutputHelper _output; - public static readonly object[][] CompressedServers = System.Net.Test.Common.Configuration.Http.CompressedServers; - public HttpClientHandler_Decompression_Test(ITestOutputHelper output) - { - _output = output; - } + public HttpClientHandler_Decompression_Test(ITestOutputHelper output) : base(output) { } public static IEnumerable DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs index e9326bd3a85a..aab74804a20a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.DefaultProxyCredentials.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -17,6 +18,8 @@ namespace System.Net.Http.Functional.Tests public abstract class HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandlerTestBase { + public HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { } + [Fact] public void Default_Get_Null() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs new file mode 100644 index 000000000000..9f8d33f86a19 --- /dev/null +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs @@ -0,0 +1,199 @@ +// 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Net.Test.Common; +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; + + public abstract class HttpClientHandlerTest_Headers : HttpClientHandlerTestBase + { + public HttpClientHandlerTest_Headers(ITestOutputHelper output) : base(output) { } + + private sealed class DerivedHttpHeaders : HttpHeaders { } + + [Fact] + public async Task SendAsync_UserAgent_CorrectlyWritten() + { + string userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.18 Safari/537.36"; + + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using (var client = CreateHttpClient()) + { + var message = new HttpRequestMessage(HttpMethod.Get, uri); + message.Headers.TryAddWithoutValidation("User-Agent", userAgent); + (await client.SendAsync(message).ConfigureAwait(false)).Dispose(); + } + }, + async server => + { + HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK); + + string agent = requestData.GetSingleHeaderValue("User-Agent"); + Assert.Equal(userAgent, agent); + }); + } + + [Theory] + [InlineData("\u05D1\u05F1")] + [InlineData("jp\u30A5")] + public async Task SendAsync_InvalidHeader_Throw(string value) + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + HttpClientHandler handler = CreateHttpClientHandler(); + using (HttpClient client = CreateHttpClient()) + { + var request = new HttpRequestMessage(HttpMethod.Get, uri); + Assert.True(request.Headers.TryAddWithoutValidation("bad", value)); + + await Assert.ThrowsAsync(() => client.SendAsync(request)); + } + + }, + async server => + { + try + { + // Client should abort at some point so this is going to throw. + HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK).ConfigureAwait(false); + } + catch (IOException) { }; + }); + } + + [Fact] + public async Task SendAsync_SpecialCharacterHeader_Success() + { + string headerValue = "header name with underscore"; + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using (var client = CreateHttpClient()) + { + var message = new HttpRequestMessage(HttpMethod.Get, uri); + message.Headers.TryAddWithoutValidation("x-Special_name", "header name with underscore"); + (await client.SendAsync(message).ConfigureAwait(false)).Dispose(); + } + }, + async server => + { + HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK); + + string header = requestData.GetSingleHeaderValue("x-Special_name"); + Assert.Equal(header, headerValue); + }); + } + + [Fact] + public async Task GetAsync_MissingExpires_ReturnNull() + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using (var client = CreateHttpClient()) + { + HttpResponseMessage response = await client.GetAsync(uri); + Assert.Null(response.Content.Headers.Expires); + } + }, + async server => + { + await server.HandleRequestAsync(HttpStatusCode.OK); + }); + } + + [Theory] + [InlineData("Thu, 01 Dec 1994 16:00:00 GMT", true)] + [InlineData("-1", false)] + [InlineData("0", false)] + public async Task SendAsync_Expires_Success(string value, bool isValid) + { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { + using (var client = CreateHttpClient()) + { + var message = new HttpRequestMessage(HttpMethod.Get, uri); + HttpResponseMessage response = await client.SendAsync(message); + Assert.NotNull(response.Content.Headers.Expires); + // Invalid date should be converted to MinValue so everything is expired. + Assert.Equal(isValid ? DateTime.Parse(value) : DateTimeOffset.MinValue, response.Content.Headers.Expires); + } + }, + async server => + { + IList headers = new HttpHeaderData[] { new HttpHeaderData("Expires", value) }; + + HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK, headers); + }); + } + + [Theory] + [InlineData("-1", false)] + [InlineData("Thu, 01 Dec 1994 16:00:00 GMT", true)] + public void HeadersAdd_CustomExpires_Success(string value, bool isValid) + { + var headers = new DerivedHttpHeaders(); + if (!isValid) + { + Assert.Throws(() => headers.Add("Expires", value)); + } + Assert.True(headers.TryAddWithoutValidation("Expires", value)); + Assert.Equal(1, Enumerable.Count(headers.GetValues("Expires"))); + Assert.Equal(value, headers.GetValues("Expires").First()); + } + + [OuterLoop("Uses external server")] + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort) + { + var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer); + m.Headers.Host = withPort ? Configuration.Http.SecureHost + ":443" : Configuration.Http.SecureHost; + + using (HttpClient client = CreateHttpClient()) + using (HttpResponseMessage response = await client.SendAsync(m)) + { + string responseContent = await response.Content.ReadAsStringAsync(); + _output.WriteLine(responseContent); + TestHelper.VerifyResponseBody( + responseContent, + response.Content.Headers.ContentMD5, + false, + null); + } + } + + [OuterLoop("Uses external server")] + [Fact] + public async Task SendAsync_GetWithInvalidHostHeader_ThrowsException() + { + if (PlatformDetection.IsNetCore && (!UseSocketsHttpHandler || LoopbackServerFactory.IsHttp2)) + { + // Only .NET Framework and SocketsHttpHandler with HTTP/1.x use the Host header to influence the SSL auth. + // Host header is not used for HTTP2 + return; + } + + var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer); + m.Headers.Host = "hostheaderthatdoesnotmatch"; + + using (HttpClient client = CreateHttpClient()) + { + await Assert.ThrowsAsync(() => client.SendAsync(m)); + } + } + } +} diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs index 3e6c56d87e77..d25d7cc7c99a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs @@ -2,10 +2,13 @@ // 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.Diagnostics; using System.Net.Test.Common; +using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -16,7 +19,9 @@ public abstract class HttpClientHandlerTest_Http2 : HttpClientHandlerTestBase public static bool SupportsAlpn => PlatformDetection.SupportsAlpn; - [ConditionalFact(nameof(SupportsAlpn))] + public HttpClientHandlerTest_Http2(ITestOutputHelper output) : base(output) { } + + [Fact] public async Task Http2_ClientPreface_Sent() { using (var server = Http2LoopbackServer.CreateServer()) @@ -30,7 +35,7 @@ public async Task Http2_ClientPreface_Sent() } } - [ConditionalFact(nameof(SupportsAlpn))] + [Fact] public async Task Http2_InitialSettings_SentAndAcked() { using (var server = Http2LoopbackServer.CreateServer()) @@ -62,7 +67,7 @@ public async Task Http2_InitialSettings_SentAndAcked() } } - [ConditionalFact(nameof(SupportsAlpn))] + [Fact] public async Task Http2_DataSentBeforeServerPreface_ProtocolError() { using (var server = Http2LoopbackServer.CreateServer()) @@ -80,7 +85,7 @@ public async Task Http2_DataSentBeforeServerPreface_ProtocolError() } } - [ConditionalFact(nameof(SupportsAlpn))] + [Fact] public async Task Http2_NoResponseBody_Success() { using (var server = Http2LoopbackServer.CreateServer()) @@ -1108,5 +1113,157 @@ public async Task Http2_MaxConcurrentStreams_LimitEnforced() Assert.Equal(HttpStatusCode.OK, response.StatusCode); } } + + [OuterLoop("Uses Task.Delay")] + [ConditionalFact(nameof(SupportsAlpn))] + public async Task Http2_WaitingForStream_Cancellation() + { + HttpClientHandler handler = CreateHttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + + using (var server = Http2LoopbackServer.CreateServer()) + using (var client = new HttpClient(handler)) + { + Task sendTask = client.GetAsync(server.Address); + + await server.EstablishConnectionAsync(); + server.IgnoreWindowUpdates(); + + // Process first request and send response. + int streamId = await server.ReadRequestHeaderAsync(); + await server.SendDefaultResponseAsync(streamId); + + HttpResponseMessage response = await sendTask; + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Change MaxConcurrentStreams setting and wait for ack. + // (We don't want to send any new requests until we receive the ack, otherwise we may have a timing issue.) + SettingsFrame settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 0 }); + await server.WriteFrameAsync(settingsFrame); + Frame settingsAckFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30)); + Assert.Equal(FrameType.Settings, settingsAckFrame.Type); + Assert.Equal(FrameFlags.Ack, settingsAckFrame.Flags); + + // Issue a new request, so that we can cancel it while it waits for a stream. + var cts = new CancellationTokenSource(); + sendTask = client.GetAsync(server.Address, cts.Token); + + // Make sure that the request makes it to the point where it's waiting for a connection. + // It's possible that we'll still initiate a cancellation before it makes it to the queue, + // but it should still behave in the same way if so. + await Task.Delay(500); + + Stopwatch stopwatch = Stopwatch.StartNew(); + cts.Cancel(); + + await Assert.ThrowsAnyAsync(async () => await sendTask); + + // Ensure that the cancellation occurs promptly + stopwatch.Stop(); + Assert.True(stopwatch.ElapsedMilliseconds < 30000); + + // As the client has not allocated a stream ID when the corresponding request is cancelled, + // we do not send a RST stream frame. + } + } + + [ConditionalFact(nameof(SupportsAlpn))] + public async Task Http2_WaitingOnWindowCredit_Cancellation() + { + // The goal of this test is to get the client into the state where it has sent the headers, + // but is waiting on window credit before it will send the body. We then issue a cancellation + // to ensure the request is cancelled as expected. + const int InitialWindowSize = 65535; + const int ContentSize = InitialWindowSize + 1; + + HttpClientHandler handler = CreateHttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + TestHelper.EnsureHttp2Feature(handler); + + var content = new ByteArrayContent(TestHelper.GenerateRandomContent(ContentSize)); + + using (var server = Http2LoopbackServer.CreateServer()) + using (var client = new HttpClient(handler)) + { + var cts = new CancellationTokenSource(); + Task clientTask = client.PostAsync(server.Address, content, cts.Token); + + await server.EstablishConnectionAsync(); + + Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30)); + int streamId = frame.StreamId; + Assert.Equal(FrameType.Headers, frame.Type); + Assert.Equal(FrameFlags.EndHeaders, frame.Flags); + + // Receive up to initial window size + int bytesReceived = 0; + while (bytesReceived < InitialWindowSize) + { + frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30)); + Assert.Equal(streamId, frame.StreamId); + Assert.Equal(FrameType.Data, frame.Type); + Assert.Equal(FrameFlags.None, frame.Flags); + Assert.True(frame.Length > 0); + + bytesReceived += frame.Length; + } + + // The client is waiting for more credit in order to send the last byte of the + // request body. Test cancellation at this point. + Stopwatch stopwatch = Stopwatch.StartNew(); + + cts.Cancel(); + await Assert.ThrowsAnyAsync(async () => await clientTask); + + // Ensure that the cancellation occurs promptly + stopwatch.Stop(); + Assert.True(stopwatch.ElapsedMilliseconds < 30000); + + // The server should receive a RstStream frame. + frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30)); + Assert.Equal(FrameType.RstStream, frame.Type); + } + } + + [OuterLoop("Uses Task.Delay")] + [ConditionalFact(nameof(SupportsAlpn))] + public async Task Http2_PendingSend_Cancellation() + { + // The goal of this test is to get the client into the state where it is sending content, + // but the send pends because the TCP window is full. + const int InitialWindowSize = 65535; + const int ContentSize = InitialWindowSize * 2; // Double the default TCP window size. + + HttpClientHandler handler = CreateHttpClientHandler(); + handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; + TestHelper.EnsureHttp2Feature(handler); + + var content = new ByteArrayContent(TestHelper.GenerateRandomContent(ContentSize)); + + using (var server = Http2LoopbackServer.CreateServer()) + using (var client = new HttpClient(handler)) + { + var cts = new CancellationTokenSource(); + + Task clientTask = client.PostAsync(server.Address, content, cts.Token); + + await server.EstablishConnectionAsync(); + + Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30)); + int streamId = frame.StreamId; + Assert.Equal(FrameType.Headers, frame.Type); + Assert.Equal(FrameFlags.EndHeaders, frame.Flags); + + // Increase the size of the HTTP/2 Window, so that it is large enough to fill the + // TCP window when we do not perform any reads on the server side. + await server.WriteFrameAsync(new WindowUpdateFrame(InitialWindowSize, streamId)); + + // Give the client time to read the window update frame, and for the write to pend. + await Task.Delay(1000); + cts.Cancel(); + + await Assert.ThrowsAnyAsync(async () => await clientTask); + } + } } } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxConnectionsPerServer.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxConnectionsPerServer.cs index df5beb48676b..c73c3a106b69 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxConnectionsPerServer.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxConnectionsPerServer.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -17,6 +18,8 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "UAP connection management behavior is different due to WinRT")] public abstract class HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandlerTestBase { + public HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { } + [Fact] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "MaxConnectionsPerServer either returns two or int.MaxValue depending if ctor of HttpClientHandlerTest executed first. Disabling cause of random xunit execution order.")] public void Default_ExpectedValue() diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxResponseHeadersLength.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxResponseHeadersLength.cs index e4532d2419a9..4ed05d620b4c 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxResponseHeadersLength.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.MaxResponseHeadersLength.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -15,6 +16,8 @@ namespace System.Net.Http.Functional.Tests public abstract class HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandlerTestBase { + public HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { } + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Not currently supported on UAP")] [Theory] [InlineData(0)] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Proxy.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Proxy.cs index a0152d5183e5..329e11fc3076 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Proxy.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Proxy.cs @@ -17,12 +17,7 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "UAP HTTP stack doesn't support .Proxy property")] public abstract class HttpClientHandler_Proxy_Test : HttpClientHandlerTestBase { - private readonly ITestOutputHelper _output; - - public HttpClientHandler_Proxy_Test(ITestOutputHelper output) - { - _output = output; - } + public HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { } [ActiveIssue(32809)] [OuterLoop("Uses external server")] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs index 596493b29636..3ba0fb51d768 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ResponseDrain.cs @@ -8,11 +8,14 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandler_ResponseDrain_Test : HttpClientHandlerTestBase { + public HttpClientHandler_ResponseDrain_Test(ITestOutputHelper output) : base(output) { } + protected virtual void SetResponseDrainTimeout(HttpClientHandler handler, TimeSpan time) { } [OuterLoop] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs index 2216045a046c..1a7e7e779176 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.ServerCertificates.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -24,6 +25,8 @@ public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpCl private bool BackendSupportsCustomCertificateHandlingAndClientSupportsDHECipherSuites => (BackendSupportsCustomCertificateHandling && ClientSupportsDHECipherSuites); + public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } + [Fact] [SkipOnTargetFramework(~TargetFrameworkMonikers.Uap)] public void Ctor_ExpectedDefaultPropertyValues_UapPlatform() diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs index 9332ae099dec..910025362ce0 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.SslProtocols.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.DotNet.XUnitExtensions; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -20,6 +21,8 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "SslProtocols property requires .NET 4.7.2")] public abstract partial class HttpClientHandler_SslProtocols_Test : HttpClientHandlerTestBase { + public HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { } + [Fact] public void DefaultProtocols_MatchesExpected() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.TrailingHeaders.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.TrailingHeaders.cs index f7e36782fac4..08b436c2bfc5 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.TrailingHeaders.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.TrailingHeaders.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -23,6 +24,8 @@ public abstract class HttpClientHandlerTest_TrailingHeaders_Test : HttpClientHan private static Frame MakeDataFrame(int streamId, byte[] data, bool endStream = false) => new DataFrame(data, (endStream ? FrameFlags.EndStream : FrameFlags.None), 0, streamId); + public HttpClientHandlerTest_TrailingHeaders_Test (ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(false)] [InlineData(true)] diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs index 63a68daa3b00..997e7b723e1a 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs @@ -29,7 +29,6 @@ namespace System.Net.Http.Functional.Tests // to separately Dispose (or have a 'using' statement) for the handler. public abstract class HttpClientHandlerTest : HttpClientHandlerTestBase { - readonly ITestOutputHelper _output; private const string ExpectedContent = "Test content"; private const string Username = "testuser"; private const string Password = "password"; @@ -64,12 +63,11 @@ private static IEnumerable GetMethods(params string[] methods) } } - public HttpClientHandlerTest(ITestOutputHelper output) + public HttpClientHandlerTest(ITestOutputHelper output) : base(output) { - _output = output; if (PlatformDetection.IsFullFramework) { - // On .NET Framework, the default limit for connections/server is very low (2). + // On .NET Framework, the default limit for connections/server is very low (2). // On .NET Core, the default limit is higher. Since these tests run in parallel, // the limit needs to be increased to avoid timeouts when running the tests. System.Net.ServicePointManager.DefaultConnectionLimit = int.MaxValue; @@ -249,48 +247,6 @@ public async Task SendAsync_SimpleGet_Success(Uri remoteServer) } } - [OuterLoop("Uses external server")] - [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task SendAsync_GetWithValidHostHeader_Success(bool withPort) - { - var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer); - m.Headers.Host = withPort ? Configuration.Http.SecureHost + ":123" : Configuration.Http.SecureHost; - - using (HttpClient client = CreateHttpClient()) - using (HttpResponseMessage response = await client.SendAsync(m)) - { - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - false, - null); - } - } - - [OuterLoop("Uses external server")] - [Fact] - public async Task SendAsync_GetWithInvalidHostHeader_ThrowsException() - { - if (PlatformDetection.IsNetCore && (!UseSocketsHttpHandler || LoopbackServerFactory.IsHttp2)) - { - // Only .NET Framework and SocketsHttpHandler with HTTP/1.x use the Host header to influence the SSL auth. - // Host header is not used for HTTP2 - return; - } - - var m = new HttpRequestMessage(HttpMethod.Get, Configuration.Http.SecureRemoteEchoServer); - m.Headers.Host = "hostheaderthatdoesnotmatch"; - - using (HttpClient client = CreateHttpClient()) - { - await Assert.ThrowsAsync(() => client.SendAsync(m)); - } - } - [ActiveIssue(22158, TargetFrameworkMonikers.Uap)] [Fact] public async Task GetAsync_IPv6LinkLocalAddressUri_Success() @@ -367,35 +323,6 @@ public async Task GetAsync_ResponseContentAfterClientAndHandlerDispose_Success() } } - [OuterLoop("Uses external server")] - [Fact] - public async Task SendAsync_Cancel_CancellationTokenPropagates() - { - var cts = new CancellationTokenSource(); - cts.Cancel(); - using (HttpClient client = CreateHttpClient()) - { - var request = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteEchoServer); - Task t = client.SendAsync(request, cts.Token); - OperationCanceledException ex; - if (PlatformDetection.IsUap) - { - ex = await Assert.ThrowsAsync(() => t); - } - else - { - ex = await Assert.ThrowsAsync(() => t); - } - - Assert.True(cts.Token.IsCancellationRequested, "cts token IsCancellationRequested"); - if (!PlatformDetection.IsFullFramework) - { - // .NET Framework has bug where it doesn't propagate token information. - Assert.True(ex.CancellationToken.IsCancellationRequested, "exception token IsCancellationRequested"); - } - } - } - [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "UAP HTTP stack doesn't support .Proxy property")] [Theory] [InlineData("[::1234]")] @@ -2275,7 +2202,13 @@ public async Task PostAsync_RedirectWith302_LargePayload() public async Task PostAsync_Redirect_LargePayload_Helper(int statusCode, bool expectRedirectToPost) { - using (var fs = new FileStream(Path.Combine(Path.GetTempPath(), Path.GetTempFileName()), FileMode.Create, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) + 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); @@ -2283,10 +2216,18 @@ public async Task PostAsync_Redirect_LargePayload_Helper(int statusCode, bool ex fs.Flush(flushToDisk: true); fs.Position = 0; - Uri redirectUri = Configuration.Http.RedirectUriForDestinationUri(false, statusCode, Configuration.Http.SecureRemoteEchoServer, 1); + Uri redirectUri = Configuration.Http.RedirectUriForDestinationUri( + secure: false, + statusCode: statusCode, + destinationUri: Configuration.Http.SecureRemoteVerifyUploadServer, + 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 = CreateHttpClient()) - using (HttpResponseMessage response = await client.PostAsync(redirectUri, new StreamContent(fs))) + using (HttpResponseMessage response = await client.PostAsync(redirectUri, content)) { try { @@ -2300,7 +2241,8 @@ public async Task PostAsync_Redirect_LargePayload_Helper(int statusCode, bool ex if (expectRedirectToPost) { - Assert.InRange(response.Content.Headers.ContentLength.GetValueOrDefault(), contentBytes.Length, int.MaxValue); + IEnumerable headerValue = response.Headers.GetValues("X-HttpRequest-Method"); + Assert.Equal("POST", headerValue.First()); } } } @@ -2674,29 +2616,6 @@ await LoopbackServer.CreateServerAsync(async (server, rootUrl) => } #endregion - [Fact] - public async Task SendAsync_UserAgent_CorrectlyWritten() - { - string userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.18 Safari/537.36"; - - await LoopbackServerFactory.CreateClientAndServerAsync(async uri => - { - using (var client = CreateHttpClient()) - { - var message = new HttpRequestMessage(HttpMethod.Get, uri); - message.Headers.TryAddWithoutValidation("User-Agent", userAgent); - (await client.SendAsync(message).ConfigureAwait(false)).Dispose(); - } - }, - async server => - { - HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK); - - string agent = requestData.GetSingleHeaderValue("User-Agent"); - Assert.Equal(userAgent, agent); - }); - } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // [ActiveIssue(11057)] public async Task GetAsync_InvalidUrl_ExpectedExceptionThrown() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.cs index 27639e810dfa..f34fdfc6b497 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTestBase.cs @@ -7,10 +7,14 @@ using System.Reflection; using System.Net.Test.Common; +using Xunit.Abstractions; + namespace System.Net.Http.Functional.Tests { public abstract class HttpClientHandlerTestBase : FileCleanupTestBase { + public readonly ITestOutputHelper _output; + protected virtual bool UseSocketsHttpHandler => true; protected virtual bool UseHttp2LoopbackServer => false; @@ -19,6 +23,11 @@ public abstract class HttpClientHandlerTestBase : FileCleanupTestBase protected bool IsNetfxHandler => PlatformDetection.IsWindows && PlatformDetection.IsFullFramework; protected bool IsUapHandler => PlatformDetection.IsWindows && PlatformDetection.IsUap; + public HttpClientHandlerTestBase(ITestOutputHelper output) + { + _output = output; + } + protected HttpClient CreateHttpClient() => new HttpClient(CreateHttpClientHandler()); protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseSocketsHttpHandler, UseHttp2LoopbackServer); @@ -47,7 +56,7 @@ protected static HttpClientHandler CreateHttpClientHandler(bool useSocketsHttpHa Debug.Assert(useSocketsHttpHandler == IsSocketsHttpHandler(handler), "Unexpected handler."); } - TestHelper.EnsureHttp2Feature(handler); + TestHelper.EnsureHttp2Feature(handler, useHttp2LoopbackServer); if (useHttp2LoopbackServer) { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs index 5d107a85fd2a..2394d6575b8f 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpClientMiniStressTest.cs @@ -9,11 +9,14 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { public abstract class HttpClientMiniStress : HttpClientHandlerTestBase { + public HttpClientMiniStress(ITestOutputHelper output) : base(output) { } + [ConditionalTheory(typeof(TestEnvironment), nameof(TestEnvironment.IsStressModeEnabled))] [MemberData(nameof(GetStressOptions))] public void SingleClient_ManyGets_Sync(int numRequests, int dop, HttpCompletionOption completionOption) diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs b/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs index 543473b87923..0a0577ecb25d 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpProtocolTests.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -16,6 +17,8 @@ public abstract class HttpProtocolTests : HttpClientHandlerTestBase protected virtual Stream GetStream(Stream s) => s; protected virtual Stream GetStream_ClientDisconnectOk(Stream s) => s; + public HttpProtocolTests(ITestOutputHelper output) : base(output) { } + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "Uap does not support 1.0")] [Fact] public async Task GetAsync_RequestVersion10_Success() @@ -642,6 +645,8 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => public abstract class HttpProtocolTests_Dribble : HttpProtocolTests { + public HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { } + protected override Stream GetStream(Stream s) => new DribbleStream(s); protected override Stream GetStream_ClientDisconnectOk(Stream s) => new DribbleStream(s, true); } diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpRequestMessageTest.cs b/src/System.Net.Http/tests/FunctionalTests/HttpRequestMessageTest.cs index dd3c419d0cdb..aa02d41664e6 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpRequestMessageTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpRequestMessageTest.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -18,6 +19,8 @@ public class HttpRequestMessageTest : HttpClientHandlerTestBase { Version _expectedRequestMessageVersion = !PlatformDetection.IsFullFramework ? new Version(2,0) : new Version(1, 1); + public HttpRequestMessageTest(ITestOutputHelper output) : base(output) { } + [Fact] public void Ctor_Default_CorrectDefaults() { diff --git a/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs b/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs index 4134768e1fdd..5a8a52644c63 100644 --- a/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/HttpRetryProtocolTests.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -19,6 +20,8 @@ public abstract class HttpRetryProtocolTests : HttpClientHandlerTestBase // Retry logic is supported by SocketsHttpHandler, CurlHandler, uap, and netfx. Only WinHttp does not support. private bool IsRetrySupported => !IsWinHttpHandler; + public HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { } + [Fact] [ActiveIssue(26770, TargetFrameworkMonikers.NetFramework)] public async Task GetAsync_RetryOnConnectionClosed_Success() diff --git a/src/System.Net.Http/tests/FunctionalTests/IdnaProtocolTests.cs b/src/System.Net.Http/tests/FunctionalTests/IdnaProtocolTests.cs index 7c073c625d77..aa82b32ba143 100644 --- a/src/System.Net.Http/tests/FunctionalTests/IdnaProtocolTests.cs +++ b/src/System.Net.Http/tests/FunctionalTests/IdnaProtocolTests.cs @@ -6,6 +6,7 @@ using System.Net.Test.Common; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace System.Net.Http.Functional.Tests { @@ -13,6 +14,8 @@ public abstract class IdnaProtocolTests : HttpClientHandlerTestBase { protected abstract bool SupportsIdna { get; } + public IdnaProtocolTests(ITestOutputHelper output) : base(output) { } + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "UAP does not support custom proxies.")] [Theory] [MemberData(nameof(InternationalHostNames))] diff --git a/src/System.Net.Http/tests/FunctionalTests/PlatformHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/PlatformHandlerTest.cs index 2720322a4274..778af074fdfb 100644 --- a/src/System.Net.Http/tests/FunctionalTests/PlatformHandlerTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/PlatformHandlerTest.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Net.Test.Common; using System.Threading.Tasks; using Xunit; @@ -12,6 +13,10 @@ namespace System.Net.Http.Functional.Tests public class PlatformHandler_HttpClientHandler : HttpClientHandlerTestBase { + protected override bool UseSocketsHttpHandler => false; + + public PlatformHandler_HttpClientHandler(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData(false)] [InlineData(true)] @@ -59,31 +64,37 @@ await TestHelper.WhenAllCompletedOrAnyFailed( public sealed class PlatformHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test { + public PlatformHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpProtocolTests : HttpProtocolTests { + public PlatformHandler_HttpProtocolTests(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble { + public PlatformHandler_HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_DiagnosticsTest : DiagnosticsTest { + public PlatformHandler_DiagnosticsTest(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClient_SelectedSites_Test : HttpClient_SelectedSites_Test { + public PlatformHandler_HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClientEKUTest : HttpClientEKUTest { + public PlatformHandler_HttpClientEKUTest(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } @@ -96,6 +107,7 @@ public PlatformHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelper o public sealed class PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test { + public PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } #endif @@ -108,16 +120,19 @@ public PlatformHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelp public sealed class PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test { + public PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test { + public PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test { + public PlatformHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } @@ -135,6 +150,7 @@ public PlatformHandler_ResponseStreamTest(ITestOutputHelper output) : base(outpu public sealed class PlatformHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test { + public PlatformHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } @@ -152,6 +168,7 @@ public PlatformHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base public sealed class PlatformHandler_HttpClientMiniStress : HttpClientMiniStress { + public PlatformHandler_HttpClientMiniStress(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } @@ -175,6 +192,7 @@ public PlatformHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(o public sealed class PlatformHandler_IdnaProtocolTests : IdnaProtocolTests { + public PlatformHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; // WinHttp on Win7 does not support IDNA protected override bool SupportsIdna => !PlatformDetection.IsWindows7 && !PlatformDetection.IsFullFramework; @@ -182,26 +200,31 @@ public sealed class PlatformHandler_IdnaProtocolTests : IdnaProtocolTests public sealed class PlatformHandler_HttpRetryProtocolTests : HttpRetryProtocolTests { + public PlatformHandler_HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandlerTest_Cookies : HttpClientHandlerTest_Cookies { + public PlatformHandlerTest_Cookies(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandlerTest_Cookies_Http11 : HttpClientHandlerTest_Cookies_Http11 { + public PlatformHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test { + public PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } public sealed class PlatformHandler_HttpClientHandler_Cancellation_Test : HttpClientHandler_Cancellation_Test { + public PlatformHandler_HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => false; } diff --git a/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs b/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs index 63f23cf9da05..f55450de67c4 100644 --- a/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/PostScenarioTest.cs @@ -25,9 +25,8 @@ public abstract class PostScenarioTest : HttpClientHandlerTestBase private static readonly Uri SecureBasicAuthServerUri = Configuration.Http.BasicAuthUriForCreds(true, UserName, Password); - private readonly ITestOutputHelper _output; - public static readonly object[][] EchoServers = Configuration.Http.EchoServers; + public static readonly object[][] VerifyUploadServers = Configuration.Http.VerifyUploadServers; public static readonly object[][] BasicAuthEchoServers = new object[][] @@ -36,10 +35,7 @@ public abstract class PostScenarioTest : HttpClientHandlerTestBase new object[] { SecureBasicAuthServerUri } }; - public PostScenarioTest(ITestOutputHelper output) - { - _output = output; - } + public PostScenarioTest(ITestOutputHelper output) : base(output) { } [ActiveIssue(30057, TargetFrameworkMonikers.Uap)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, ".NET Framework disposes request content after send")] @@ -67,7 +63,7 @@ public async Task PostRewindableStreamContentMultipleTimes_StreamContentFullySen } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostNoContentUsingContentLengthSemantics_Success(Uri serverUri) { await PostHelper(serverUri, string.Empty, null, @@ -75,7 +71,7 @@ await PostHelper(serverUri, string.Empty, null, } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostEmptyContentUsingContentLengthSemantics_Success(Uri serverUri) { await PostHelper(serverUri, string.Empty, new StringContent(string.Empty), @@ -83,7 +79,7 @@ public async Task PostEmptyContentUsingContentLengthSemantics_Success(Uri server } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostEmptyContentUsingChunkedEncoding_Success(Uri serverUri) { await PostHelper(serverUri, string.Empty, new StringContent(string.Empty), @@ -91,7 +87,7 @@ public async Task PostEmptyContentUsingChunkedEncoding_Success(Uri serverUri) } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostEmptyContentUsingConflictingSemantics_Success(Uri serverUri) { await PostHelper(serverUri, string.Empty, new StringContent(string.Empty), @@ -99,7 +95,7 @@ public async Task PostEmptyContentUsingConflictingSemantics_Success(Uri serverUr } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostUsingContentLengthSemantics_Success(Uri serverUri) { await PostHelper(serverUri, ExpectedContent, new StringContent(ExpectedContent), @@ -107,7 +103,7 @@ public async Task PostUsingContentLengthSemantics_Success(Uri serverUri) } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostUsingChunkedEncoding_Success(Uri serverUri) { await PostHelper(serverUri, ExpectedContent, new StringContent(ExpectedContent), @@ -115,7 +111,7 @@ public async Task PostUsingChunkedEncoding_Success(Uri serverUri) } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostSyncBlockingContentUsingChunkedEncoding_Success(Uri serverUri) { await PostHelper(serverUri, ExpectedContent, new SyncBlockingContent(ExpectedContent), @@ -123,7 +119,7 @@ public async Task PostSyncBlockingContentUsingChunkedEncoding_Success(Uri server } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostRepeatedFlushContentUsingChunkedEncoding_Success(Uri serverUri) { await PostHelper(serverUri, ExpectedContent, new RepeatedFlushContent(ExpectedContent), @@ -131,7 +127,7 @@ public async Task PostRepeatedFlushContentUsingChunkedEncoding_Success(Uri serve } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] public async Task PostUsingUsingConflictingSemantics_UsesChunkedSemantics(Uri serverUri) { await PostHelper(serverUri, ExpectedContent, new StringContent(ExpectedContent), @@ -139,7 +135,7 @@ public async Task PostUsingUsingConflictingSemantics_UsesChunkedSemantics(Uri se } [OuterLoop("Uses external servers")] - [Theory, MemberData(nameof(EchoServers))] + [Theory, MemberData(nameof(VerifyUploadServers))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "netfx behaves differently and will buffer content and use 'Content-Length' semantics")] [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "WinRT behaves differently and will use 'Content-Length' semantics")] public async Task PostUsingNoSpecifiedSemantics_UsesChunkedSemantics(Uri serverUri) @@ -148,9 +144,9 @@ public async Task PostUsingNoSpecifiedSemantics_UsesChunkedSemantics(Uri serverU useContentLengthUpload: false, useChunkedEncodingUpload: false); } - public static IEnumerable EchoServersAndLargeContentSizes() + public static IEnumerable VerifyUploadServersAndLargeContentSizes() { - foreach (Uri uri in Configuration.Http.EchoServerList) + foreach (Uri uri in Configuration.Http.VerifyUploadServerList) { yield return new object[] { uri, 5 * 1024 }; yield return new object[] { uri, 63 * 1024 }; @@ -160,7 +156,7 @@ public static IEnumerable EchoServersAndLargeContentSizes() [OuterLoop("Uses external server")] [Theory] - [MemberData(nameof(EchoServersAndLargeContentSizes))] + [MemberData(nameof(VerifyUploadServersAndLargeContentSizes))] public async Task PostLargeContentUsingContentLengthSemantics_Success(Uri serverUri, int contentLength) { var rand = new Random(42); @@ -246,8 +242,12 @@ private async Task PostHelper( { requestContent.Headers.ContentLength = null; } + + // Compute MD5 of request body data. This will be verified by the server when it + // receives the request. + requestContent.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(requestBody); } - + if (useChunkedEncodingUpload) { client.DefaultRequestHeaders.TransferEncodingChunked = true; @@ -256,19 +256,6 @@ private async Task PostHelper( using (HttpResponseMessage response = await client.PostAsync(serverUri, requestContent)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); - string responseContent = await response.Content.ReadAsStringAsync(); - _output.WriteLine(responseContent); - - if (!useContentLengthUpload && !useChunkedEncodingUpload) - { - useChunkedEncodingUpload = true; - } - - TestHelper.VerifyResponseBody( - responseContent, - response.Content.Headers.ContentMD5, - useChunkedEncodingUpload, - requestBody); } } } diff --git a/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs b/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs index 9e76fc223492..76a3ce3642ae 100644 --- a/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/PostScenarioUWPTest.cs @@ -18,12 +18,7 @@ namespace System.Net.Http.Functional.Tests public abstract class PostScenarioUWPTest : HttpClientHandlerTestBase { - private readonly ITestOutputHelper _output; - - public PostScenarioUWPTest(ITestOutputHelper output) - { - _output = output; - } + public PostScenarioUWPTest(ITestOutputHelper output) : base(output) { } [Fact] public void Authentication_UseStreamContent_Throws() diff --git a/src/System.Net.Http/tests/FunctionalTests/ResponseStreamTest.cs b/src/System.Net.Http/tests/FunctionalTests/ResponseStreamTest.cs index 682e225386a0..b6651770b131 100644 --- a/src/System.Net.Http/tests/FunctionalTests/ResponseStreamTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/ResponseStreamTest.cs @@ -17,12 +17,7 @@ namespace System.Net.Http.Functional.Tests public abstract class ResponseStreamTest : HttpClientHandlerTestBase { - private readonly ITestOutputHelper _output; - - public ResponseStreamTest(ITestOutputHelper output) - { - _output = output; - } + public ResponseStreamTest(ITestOutputHelper output) : base(output) { } [OuterLoop("Uses external server")] [Theory] @@ -32,6 +27,8 @@ public ResponseStreamTest(ITestOutputHelper output) [InlineData(3)] [InlineData(4)] [InlineData(5)] + [InlineData(6)] + [InlineData(7)] public async Task GetStreamAsync_ReadToEnd_Success(int readMode) { using (HttpClient client = CreateHttpClient()) @@ -87,6 +84,22 @@ public async Task GetStreamAsync_ReadToEnd_Success(int readMode) break; case 5: + // ReadByte + int byteValue; + while ((byteValue = stream.ReadByte()) != -1) + { + ms.WriteByte((byte)byteValue); + } + responseBody = Encoding.UTF8.GetString(ms.ToArray()); + break; + + case 6: + // CopyTo + stream.CopyTo(ms); + responseBody = Encoding.UTF8.GetString(ms.ToArray()); + break; + + case 7: // CopyToAsync await stream.CopyToAsync(ms); responseBody = Encoding.UTF8.GetString(ms.ToArray()); diff --git a/src/System.Net.Http/tests/FunctionalTests/SchSendAuxRecordHttpTest.cs b/src/System.Net.Http/tests/FunctionalTests/SchSendAuxRecordHttpTest.cs index 0bf5de2702bd..61a1a5253d91 100644 --- a/src/System.Net.Http/tests/FunctionalTests/SchSendAuxRecordHttpTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/SchSendAuxRecordHttpTest.cs @@ -15,12 +15,7 @@ namespace System.Net.Http.Functional.Tests [SkipOnTargetFramework(TargetFrameworkMonikers.Uap, "HttpsTestServer not compatible on UAP")] public abstract class SchSendAuxRecordHttpTest : HttpClientHandlerTestBase { - readonly ITestOutputHelper _output; - - public SchSendAuxRecordHttpTest(ITestOutputHelper output) - { - _output = output; - } + public SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { } [Fact] [PlatformSpecific(TestPlatforms.Windows)] diff --git a/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs index c11693c2fa97..80a7a30b24f8 100644 --- a/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs +++ b/src/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs @@ -25,6 +25,8 @@ public sealed class SocketsHttpHandler_HttpClientHandler_Asynchrony_Test : HttpC { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { } + [OuterLoop("Relies on finalization")] [Fact] public async Task ExecutionContext_HttpConnectionLifetimeDoesntKeepContextAlive() @@ -91,6 +93,8 @@ public sealed class SocketsHttpHandler_HttpProtocolTests : HttpProtocolTests { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_HttpProtocolTests(ITestOutputHelper output) : base(output) { } + [Theory] [InlineData("delete", "DELETE")] [InlineData("options", "OPTIONS")] @@ -102,21 +106,25 @@ public Task CustomMethod_SentUppercasedIfKnown_Additional(string specifiedMethod public sealed class SocketsHttpHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble { + public SocketsHttpHandler_HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandler_DiagnosticsTest : DiagnosticsTest { + public SocketsHttpHandler_DiagnosticsTest(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandler_HttpClient_SelectedSites_Test : HttpClient_SelectedSites_Test { + public SocketsHttpHandler_HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandler_HttpClientEKUTest : HttpClientEKUTest { + public SocketsHttpHandler_HttpClientEKUTest(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -128,6 +136,7 @@ public SocketsHttpHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelpe public sealed class SocketsHttpHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test { + public SocketsHttpHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -139,6 +148,7 @@ public SocketsHttpHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputH public sealed class SocketsHttpHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test { + public SocketsHttpHandler_HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -146,6 +156,8 @@ public sealed class SocketsHttpHandler_HttpClientHandler_MaxConnectionsPerServer { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { } + [OuterLoop("Incurs a small delay")] [Theory] [InlineData(0)] @@ -192,6 +204,7 @@ await server.AcceptConnectionAsync(async connection => public sealed class SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test { + public SocketsHttpHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -206,6 +219,8 @@ protected override void SetResponseDrainTimeout(HttpClientHandler handler, TimeS s.ResponseDrainTimeout = time; } + public SocketsHttpHandler_HttpClientHandler_ResponseDrain_Test(ITestOutputHelper output) : base(output) { } + [Fact] public void MaxResponseDrainSize_Roundtrips() { @@ -452,6 +467,7 @@ public SocketsHttpHandler_ResponseStreamTest(ITestOutputHelper output) : base(ou public sealed class SocketsHttpHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test { + public SocketsHttpHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -463,6 +479,7 @@ public SocketsHttpHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) public sealed class SocketsHttpHandler_HttpClientHandler_TrailingHeaders_Test : HttpClientHandlerTest_TrailingHeaders_Test { + public SocketsHttpHandler_HttpClientHandler_TrailingHeaders_Test(ITestOutputHelper output) : base(output) { } // PlatformHandlers don't support trailers. protected override bool UseSocketsHttpHandler => true; } @@ -475,6 +492,7 @@ public SocketsHttpHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : b public sealed class SocketsHttpHandler_HttpClientMiniStress : HttpClientMiniStress { + public SocketsHttpHandler_HttpClientMiniStress(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -498,27 +516,32 @@ public SocketsHttpHandler_DefaultCredentialsTest(ITestOutputHelper output) : bas public sealed class SocketsHttpHandler_IdnaProtocolTests : IdnaProtocolTests { + public SocketsHttpHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; protected override bool SupportsIdna => true; } public sealed class SocketsHttpHandler_HttpRetryProtocolTests : HttpRetryProtocolTests { + public SocketsHttpHandler_HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandlerTest_Cookies : HttpClientHandlerTest_Cookies { + public SocketsHttpHandlerTest_Cookies(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandlerTest_Cookies_Http11 : HttpClientHandlerTest_Cookies_Http11 { + public SocketsHttpHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test : HttpClientHandler_Cancellation_Test { + public SocketsHttpHandler_HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; [Fact] @@ -692,6 +715,7 @@ protected override Task SerializeToStreamAsync(Stream stream, TransportContext c public sealed class SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test { + public SocketsHttpHandler_HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } @@ -741,6 +765,8 @@ public sealed class SocketsHttpHandler_ConnectionUpgrade_Test : HttpClientHandle { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_ConnectionUpgrade_Test(ITestOutputHelper output) : base(output) { } + [Fact] public async Task UpgradeConnection_ReturnsReadableAndWritableStream() { @@ -860,6 +886,8 @@ public sealed class SocketsHttpHandler_Connect_Test : HttpClientHandlerTestBase { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_Connect_Test(ITestOutputHelper output) : base(output) { } + [Fact] public async Task ConnectMethod_Success() { @@ -948,6 +976,8 @@ public sealed class SocketsHttpHandler_HttpClientHandler_ConnectionPooling_Test { protected override bool UseSocketsHttpHandler => true; + public SocketsHttpHandler_HttpClientHandler_ConnectionPooling_Test(ITestOutputHelper output) : base(output) { } + [Fact] public async Task MultipleIterativeRequests_SameConnectionReused() { @@ -1567,6 +1597,8 @@ public async Task AfterDisposeSendAsync_GettersUsable_SettersThrow(bool dispose) public sealed class SocketsHttpHandler_ExternalConfiguration_Test : HttpClientHandlerTestBase { + public SocketsHttpHandler_ExternalConfiguration_Test(ITestOutputHelper output) : base(output) { } + private const string EnvironmentVariableSettingName = "DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER"; private const string AppContextSettingName = "System.Net.Http.UseSocketsHttpHandler"; @@ -1647,12 +1679,14 @@ public void HttpClientHandler_AppContextOverridesEnvironmentVariable() public sealed class SocketsHttpHandlerTest_Http2 : HttpClientHandlerTest_Http2 { + public SocketsHttpHandlerTest_Http2(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; } [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] public sealed class SocketsHttpHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies { + public SocketsHttpHandlerTest_Cookies_Http2(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; protected override bool UseHttp2LoopbackServer => true; } @@ -1661,7 +1695,28 @@ public sealed class SocketsHttpHandlerTest_Cookies_Http2 : HttpClientHandlerTest public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Http2 : HttpClientHandlerTest { public SocketsHttpHandlerTest_HttpClientHandlerTest_Http2(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + protected override bool UseHttp2LoopbackServer => true; + } + public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http11 : HttpClientHandlerTest_Headers + { + public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http11(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + } + + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] + public sealed class SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http2 : HttpClientHandlerTest_Headers + { + public SocketsHttpHandlerTest_HttpClientHandlerTest_Headers_Http2(ITestOutputHelper output) : base(output) { } + protected override bool UseSocketsHttpHandler => true; + protected override bool UseHttp2LoopbackServer => true; + } + + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] + public sealed class SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http2 : HttpClientHandler_Cancellation_Test + { + public SocketsHttpHandler_HttpClientHandler_Cancellation_Test_Http2(ITestOutputHelper output) : base(output) { } protected override bool UseSocketsHttpHandler => true; protected override bool UseHttp2LoopbackServer => true; } diff --git a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj index 8f4ce481fd2f..7ad68e593fbf 100644 --- a/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj +++ b/src/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj @@ -65,6 +65,9 @@ Common\System\Net\VerboseTestLogging.cs + + Common\System\Net\Http\LoopbackProxyServer.cs + Common\System\Net\Http\LoopbackServer.cs @@ -114,12 +117,12 @@ + - @@ -169,7 +172,7 @@ - + diff --git a/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs b/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs index ed800d2fd8a2..bd675de18406 100644 --- a/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs +++ b/src/System.Net.Http/tests/FunctionalTests/TestHelper.cs @@ -118,7 +118,7 @@ public static IPAddress GetIPv6LinkLocalAddress() => .Where(a => a.IsIPv6LinkLocal) .FirstOrDefault(); - public static void EnsureHttp2Feature(HttpClientHandler handler) + public static void EnsureHttp2Feature(HttpClientHandler handler, bool useHttp2LoopbackServer = true) { // All .NET Core implementations of HttpClientHandler have HTTP/2 enabled by default except when using // SocketsHttpHandler. Right now, the HTTP/2 feature is disabled on SocketsHttpHandler unless certain @@ -155,6 +155,15 @@ public static void EnsureHttp2Feature(HttpClientHandler handler) "_maxHttpVersion", BindingFlags.NonPublic | BindingFlags.Instance); field_maxHttpVersion.SetValue(_settings, new Version(2, 0)); + + if (useHttp2LoopbackServer && !PlatformDetection.SupportsAlpn) + { + // Allow HTTP/2.0 via unencrypted socket if ALPN is not supported on platform. + FieldInfo field_allowPlainHttp2 = type_HttpConnectionSettings.GetField( + "_allowUnencryptedHttp2", + BindingFlags.NonPublic | BindingFlags.Instance); + field_allowPlainHttp2.SetValue(_settings, !PlatformDetection.SupportsAlpn); + } } public static bool NativeHandlerSupportsSslConfiguration() diff --git a/src/System.Net.Http/tests/UnitTests/DigestAuthenticationTests.cs b/src/System.Net.Http/tests/UnitTests/DigestAuthenticationTests.cs index f339ba78e4ce..45d03712a7f3 100644 --- a/src/System.Net.Http/tests/UnitTests/DigestAuthenticationTests.cs +++ b/src/System.Net.Http/tests/UnitTests/DigestAuthenticationTests.cs @@ -56,5 +56,20 @@ public async void DigestResponse_AuthToken_Handling(string response, bool expect Assert.Equal(expectedResult, parameter != null); } + + [Theory] + [InlineData("test", "username=\"test\"")] + [InlineData("test@example.org", "username=\"test@example.org\"")] + [InlineData("test\"example.org", "username=\"test\\\"example.org\"")] + [InlineData("t\u00E6st", "username*=utf-8''t%C3%A6st")] + [InlineData("\uD834\uDD1E", "username*=utf-8''%F0%9D%84%9E")] + public async void DigestResponse_UserName_Encoding(string username, string encodedUserName) + { + NetworkCredential credential = new NetworkCredential(username, "bar"); + AuthenticationHelper.DigestResponse digestResponse = new AuthenticationHelper.DigestResponse("realm=\"NetCore\", nonce=\"qMRqWgAAAAAQMjIABgAAAFwEiEwAAAAA\""); + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://microsoft.com/"); + string parameter = await AuthenticationHelper.GetDigestTokenForCredential(credential, request, digestResponse).ConfigureAwait(false); + Assert.StartsWith(encodedUserName, parameter); + } } } diff --git a/src/System.Net.HttpListener/tests/HttpListenerResponseTests.Cookies.cs b/src/System.Net.HttpListener/tests/HttpListenerResponseTests.Cookies.cs index a6189118be13..0eec1399e6fd 100644 --- a/src/System.Net.HttpListener/tests/HttpListenerResponseTests.Cookies.cs +++ b/src/System.Net.HttpListener/tests/HttpListenerResponseTests.Cookies.cs @@ -57,6 +57,18 @@ public static IEnumerable Cookies_TestData() 144, "Set-Cookie: name=value", null }; + if (!PlatformDetection.IsFullFramework) + { + yield return new object[] + { + new CookieCollection() + { + new Cookie("foo bar", "value") + }, + 147, "Set-Cookie: foo bar=value", null + }; + } + yield return new object[] { new CookieCollection() diff --git a/src/System.Net.Primitives/src/System/Net/Cookie.cs b/src/System.Net.Primitives/src/System/Net/Cookie.cs index fb3d10662457..04a51e372096 100644 --- a/src/System.Net.Primitives/src/System/Net/Cookie.cs +++ b/src/System.Net.Primitives/src/System/Net/Cookie.cs @@ -41,7 +41,8 @@ public sealed class Cookie internal const string SpecialAttributeLiteral = "$"; internal static readonly char[] PortSplitDelimiters = new char[] { ' ', ',', '\"' }; - internal static readonly char[] ReservedToName = new char[] { ' ', '\t', '\r', '\n', '=', ';', ',' }; + // Space (' ') should be reserved as well per RFCs, but major web browsers support it and some web sites use it - so we support it too + internal static readonly char[] ReservedToName = new char[] { '\t', '\r', '\n', '=', ';', ',' }; internal static readonly char[] ReservedToValue = new char[] { ';', ',' }; private string m_comment = string.Empty; // Do not rename (binary serialization) @@ -255,7 +256,7 @@ block in toolchain.Networking team will be working on exposing methods from S.Ne #endif bool InternalSetName(string value) { - if (string.IsNullOrEmpty(value) || value[0] == '$' || value.IndexOfAny(ReservedToName) != -1) + if (string.IsNullOrEmpty(value) || value[0] == '$' || value.IndexOfAny(ReservedToName) != -1 || value[0] == ' ' || value[value.Length - 1] == ' ') { m_name = string.Empty; return false; @@ -370,7 +371,7 @@ internal bool VerifySetDefaults(CookieVariant variant, Uri uri, bool isLocalDoma } // Check the name - if (m_name == null || m_name.Length == 0 || m_name[0] == '$' || m_name.IndexOfAny(ReservedToName) != -1) + if (string.IsNullOrEmpty(m_name) || m_name[0] == '$' || m_name.IndexOfAny(ReservedToName) != -1 || m_name[0] == ' ' || m_name[m_name.Length - 1] == ' ') { if (shouldThrow) { diff --git a/src/System.Net.Primitives/tests/FunctionalTests/CookieTest.cs b/src/System.Net.Primitives/tests/FunctionalTests/CookieTest.cs index a0fa79429e2e..3d8b06d73e37 100644 --- a/src/System.Net.Primitives/tests/FunctionalTests/CookieTest.cs +++ b/src/System.Net.Primitives/tests/FunctionalTests/CookieTest.cs @@ -144,11 +144,18 @@ public static void Expires_GetSet_Success() [Fact] public static void Name_GetSet_Success() { - Cookie c = new Cookie(); - Assert.Equal(string.Empty, c.Name); + Cookie c1 = new Cookie(); + Assert.Equal(string.Empty, c1.Name); - c.Name = "hello"; - Assert.Equal("hello", c.Name); + c1.Name = "hello"; + Assert.Equal("hello", c1.Name); + + if (!PlatformDetection.IsFullFramework) + { + Cookie c2 = new Cookie(); + c2.Name = "hello world"; + Assert.Equal("hello world", c2.Name); + } } [Theory] @@ -156,6 +163,7 @@ public static void Name_GetSet_Success() [InlineData("")] [InlineData("$hello")] [InlineData("hello ")] + [InlineData(" hello")] [InlineData("hello\t")] [InlineData("hello\r")] [InlineData("hello\n")] diff --git a/src/System.Net.Primitives/tests/UnitTests/CookieContainerTest.cs b/src/System.Net.Primitives/tests/UnitTests/CookieContainerTest.cs index 810465d3b1e4..389b634b3336 100644 --- a/src/System.Net.Primitives/tests/UnitTests/CookieContainerTest.cs +++ b/src/System.Net.Primitives/tests/UnitTests/CookieContainerTest.cs @@ -331,6 +331,19 @@ public static IEnumerable SetCookiesData() new Cookie("_m_ask_fm_session", "session1") } }; // Empty header followed by another empty header at the end + + if (!PlatformDetection.IsFullFramework) + { + yield return new object[] + { + uSecure, + "hello world=value", + new Cookie[] + { + new Cookie("hello world", "value"), + } + }; // Name with space in it + } } [Theory] diff --git a/src/System.Net.Security/ref/System.Net.Security.cs b/src/System.Net.Security/ref/System.Net.Security.cs index 01cff41b35ab..eec818d0efa1 100644 --- a/src/System.Net.Security/ref/System.Net.Security.cs +++ b/src/System.Net.Security/ref/System.Net.Security.cs @@ -117,6 +117,7 @@ public SslClientAuthenticationOptions() { } public bool AllowRenegotiation { get { throw null; } set { } } public System.Collections.Generic.List ApplicationProtocols { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } + public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509CertificateCollection ClientCertificates { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public System.Net.Security.EncryptionPolicy EncryptionPolicy { get { throw null; } set { } } @@ -130,6 +131,7 @@ public SslServerAuthenticationOptions() { } public bool AllowRenegotiation { get { throw null; } set { } } public System.Collections.Generic.List ApplicationProtocols { get { throw null; } set { } } public System.Security.Cryptography.X509Certificates.X509RevocationMode CertificateRevocationCheckMode { get { throw null; } set { } } + public System.Net.Security.CipherSuitesPolicy CipherSuitesPolicy { get { throw null; } set { } } public bool ClientCertificateRequired { get { throw null; } set { } } public System.Security.Authentication.SslProtocols EnabledSslProtocols { get { throw null; } set { } } public System.Net.Security.EncryptionPolicy EncryptionPolicy { get { throw null; } set { } } @@ -213,6 +215,13 @@ public override void Write(byte[] buffer, int offset, int count) { } public override System.Threading.Tasks.Task WriteAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } + public sealed class CipherSuitesPolicy + { + [System.CLSCompliantAttribute(false)] + public CipherSuitesPolicy(System.Collections.Generic.IEnumerable allowedCipherSuites) { } + [System.CLSCompliantAttribute(false)] + public System.Collections.Generic.IEnumerable AllowedCipherSuites { get; } + } [System.CLSCompliantAttribute(false)] public enum TlsCipherSuite : ushort { diff --git a/src/System.Net.Security/src/Resources/Strings.resx b/src/System.Net.Security/src/Resources/Strings.resx index 40c7686f2d92..7270ea80faef 100644 --- a/src/System.Net.Security/src/Resources/Strings.resx +++ b/src/System.Net.Security/src/Resources/Strings.resx @@ -437,4 +437,7 @@ The '{0}' option was already set in the SslStream constructor. + + CipherSuitesPolicy is not supported on this platform. + diff --git a/src/System.Net.Security/src/System.Net.Security.csproj b/src/System.Net.Security/src/System.Net.Security.csproj index ec3a4f7dfd5e..d01de8187803 100644 --- a/src/System.Net.Security/src/System.Net.Security.csproj +++ b/src/System.Net.Security/src/System.Net.Security.csproj @@ -23,6 +23,7 @@ + @@ -142,6 +143,7 @@ + @@ -318,6 +320,7 @@ + @@ -437,6 +440,7 @@ + @@ -446,6 +450,7 @@ + diff --git a/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.cs new file mode 100644 index 000000000000..b07f85b0b3de --- /dev/null +++ b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicy.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. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Net.Security +{ + /// + /// Specifies allowed cipher suites. + /// + public sealed partial class CipherSuitesPolicy + { + internal CipherSuitesPolicyPal Pal { get; private set; } + + [CLSCompliant(false)] + public CipherSuitesPolicy(IEnumerable allowedCipherSuites) + { + if (allowedCipherSuites == null) + { + throw new ArgumentNullException(nameof(allowedCipherSuites)); + } + + Pal = new CipherSuitesPolicyPal(allowedCipherSuites); + } + + [CLSCompliant(false)] + public IEnumerable AllowedCipherSuites + { + get + { + // This method is only useful only for diagnostic purposes so perf is not important + // We do not want users to be able to cast result to something they can modify + foreach (TlsCipherSuite cs in Pal.GetCipherSuites()) + { + yield return cs; + } + } + } + } +} diff --git a/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs new file mode 100644 index 000000000000..a0044b77ed14 --- /dev/null +++ b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Linux.cs @@ -0,0 +1,231 @@ +// 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 Microsoft.Win32.SafeHandles; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Security.Authentication; +using System.Text; +using Ssl = Interop.Ssl; +using OpenSsl = Interop.OpenSsl; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + private static readonly byte[] RequireEncryptionDefault = + Encoding.ASCII.GetBytes("DEFAULT\0"); + + private static readonly byte[] AllowNoEncryptionDefault = + Encoding.ASCII.GetBytes("ALL:eNULL\0"); + + private static readonly byte[] NoEncryptionDefault = + Encoding.ASCII.GetBytes("eNULL\0"); + + private byte[] _cipherSuites; + private byte[] _tls13CipherSuites; + private List _tlsCipherSuites = new List(); + + internal IEnumerable GetCipherSuites() => _tlsCipherSuites; + + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + if (!Interop.Ssl.Tls13Supported) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method)) + { + if (innerContext.IsInvalid) + { + throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); + } + + using (SafeSslHandle ssl = SafeSslHandle.Create(innerContext, false)) + { + if (ssl.IsInvalid) + { + throw OpenSsl.CreateSslException(SR.net_allocate_ssl_context_failed); + } + + using (var tls13CipherSuites = new OpenSslStringBuilder()) + using (var cipherSuites = new OpenSslStringBuilder()) + { + foreach (TlsCipherSuite cs in allowedCipherSuites) + { + string name = Interop.Ssl.GetOpenSslCipherSuiteName( + ssl, + cs, + out bool isTls12OrLower); + + if (name == null) + { + // we do not have a corresponding name + // allowing less than user requested is OK + continue; + } + + _tlsCipherSuites.Add(cs); + (isTls12OrLower ? cipherSuites : tls13CipherSuites).AllowCipherSuite(name); + } + + _cipherSuites = cipherSuites.GetOpenSslString(); + _tls13CipherSuites = tls13CipherSuites.GetOpenSslString(); + } + } + } + } + + internal static bool ShouldOptOutOfTls13(CipherSuitesPolicy policy, EncryptionPolicy encryptionPolicy) + { + // if TLS 1.3 was explicitly requested the underlying code will throw + // if default option (SslProtocols.None) is used we will opt-out of TLS 1.3 + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + // TLS 1.3 uses different ciphersuite restrictions than previous versions. + // It has no equivalent to a NoEncryption option. + return true; + } + + if (policy == null) + { + // null means default, by default OpenSSL will choose if it wants to opt-out or not + return false; + } + + Debug.Assert( + policy.Pal._tls13CipherSuites.Length != 0 && + policy.Pal._tls13CipherSuites[policy.Pal._tls13CipherSuites.Length - 1] == 0, + "null terminated string expected"); + + // we should opt out only when policy is empty + return policy.Pal._tls13CipherSuites.Length == 1; + } + + internal static bool ShouldOptOutOfLowerThanTls13(CipherSuitesPolicy policy, EncryptionPolicy encryptionPolicy) + { + if (policy == null) + { + // null means default, by default OpenSSL will choose if it wants to opt-out or not + return false; + } + + Debug.Assert( + policy.Pal._cipherSuites.Length != 0 && + policy.Pal._cipherSuites[policy.Pal._cipherSuites.Length - 1] == 0, + "null terminated string expected"); + + // we should opt out only when policy is empty + return policy.Pal._cipherSuites.Length == 1; + } + + private static bool IsOnlyTls13(SslProtocols protocols) + => protocols == SslProtocols.Tls13; + + internal static bool WantsTls13(SslProtocols protocols) + => protocols == SslProtocols.None || (protocols & SslProtocols.Tls13) != 0; + + internal static byte[] GetOpenSslCipherList( + CipherSuitesPolicy policy, + SslProtocols protocols, + EncryptionPolicy encryptionPolicy) + { + if (IsOnlyTls13(protocols)) + { + // older cipher suites will be disabled through protocols + return null; + } + + if (policy == null) + { + return CipherListFromEncryptionPolicy(encryptionPolicy); + } + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + return policy.Pal._cipherSuites; + } + + internal static byte[] GetOpenSslCipherSuites( + CipherSuitesPolicy policy, + SslProtocols protocols, + EncryptionPolicy encryptionPolicy) + { + if (!WantsTls13(protocols) || policy == null) + { + // do not call TLS 1.3 API, let OpenSSL choose what to do + return null; + } + + if (encryptionPolicy == EncryptionPolicy.NoEncryption) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + return policy.Pal._tls13CipherSuites; + } + + private static byte[] CipherListFromEncryptionPolicy(EncryptionPolicy policy) + { + switch (policy) + { + case EncryptionPolicy.RequireEncryption: + return RequireEncryptionDefault; + case EncryptionPolicy.AllowNoEncryption: + return AllowNoEncryptionDefault; + case EncryptionPolicy.NoEncryption: + return NoEncryptionDefault; + default: + Debug.Fail($"Unknown EncryptionPolicy value ({policy})"); + return null; + } + } + + private class OpenSslStringBuilder : StreamWriter + { + private const string SSL_TXT_Separator = ":"; + private static readonly byte[] EmptyString = new byte[1] { 0 }; + + private MemoryStream _ms; + private bool _first = true; + + public OpenSslStringBuilder() : base(new MemoryStream(), Encoding.ASCII) + { + _ms = (MemoryStream)BaseStream; + } + + public void AllowCipherSuite(string cipherSuite) + { + if (_first) + { + _first = false; + } + else + { + Write(SSL_TXT_Separator); + } + + Write(cipherSuite); + } + + public byte[] GetOpenSslString() + { + if (_first) + { + return EmptyString; + } + + Flush(); + _ms.WriteByte(0); + return _ms.ToArray(); + } + } + } +} diff --git a/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.cs new file mode 100644 index 000000000000..608364f2da12 --- /dev/null +++ b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.OSX.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. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + internal uint[] TlsCipherSuites { get; private set; } + + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + TlsCipherSuites = allowedCipherSuites.Select((cs) => (uint)cs).ToArray(); + } + + internal IEnumerable GetCipherSuites() + { + return TlsCipherSuites.Select((cs) => (TlsCipherSuite)cs); + } + } +} diff --git a/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs new file mode 100644 index 000000000000..9d0d6141bdef --- /dev/null +++ b/src/System.Net.Security/src/System/Net/Security/CipherSuitesPolicyPal.Windows.cs @@ -0,0 +1,18 @@ +// 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.Collections.Generic; + +namespace System.Net.Security +{ + internal class CipherSuitesPolicyPal + { + internal CipherSuitesPolicyPal(IEnumerable allowedCipherSuites) + { + throw new PlatformNotSupportedException(SR.net_ssl_ciphersuites_policy_not_supported); + } + + internal IEnumerable GetCipherSuites() => null; + } +} diff --git a/src/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs b/src/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs index cba6225670a9..82a273ac3ecc 100644 --- a/src/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs +++ b/src/System.Net.Security/src/System/Net/Security/Pal.OSX/SafeDeleteSslContext.cs @@ -29,6 +29,8 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication try { + int osStatus; + unsafe { _readCallback = ReadFromConnection; @@ -37,7 +39,7 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication _sslContext = CreateSslContext(credential, sslAuthenticationOptions.IsServer); - int osStatus = Interop.AppleCrypto.SslSetIoCallbacks( + osStatus = Interop.AppleCrypto.SslSetIoCallbacks( _sslContext, _readCallback, _writeCallback); @@ -47,6 +49,27 @@ public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthentication throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); } + if (sslAuthenticationOptions.CipherSuitesPolicy != null) + { + uint[] tlsCipherSuites = sslAuthenticationOptions.CipherSuitesPolicy.Pal.TlsCipherSuites; + + unsafe + { + fixed (uint* cipherSuites = tlsCipherSuites) + { + osStatus = Interop.AppleCrypto.SslSetEnabledCipherSuites( + _sslContext, + cipherSuites, + tlsCipherSuites.Length); + + if (osStatus != 0) + { + throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); + } + } + } + } + if (sslAuthenticationOptions.ApplicationProtocols != null) { // On OSX coretls supports only client side. For server, we will silently ignore the option. @@ -314,7 +337,7 @@ private static void SetProtocols(SafeSslHandle sslContext, SslProtocols protocol // If we didn't find an unset protocol after the min, go all the way to the last one. if (maxProtocolId == (SslProtocols)(-1)) { - maxProtocolId = orderedSslProtocols[orderedSslProtocols.Length - 1]; + maxProtocolId = orderedSslProtocols[orderedSslProtocols.Length - 1]; } // Finally set this min and max. diff --git a/src/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs index 7b64a7fecf9e..13fd8be68f6d 100644 --- a/src/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs +++ b/src/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs @@ -27,6 +27,7 @@ internal SslAuthenticationOptions(SslClientAuthenticationOptions sslClientAuthen CertSelectionDelegate = localCallback; CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode; ClientCertificates = sslClientAuthenticationOptions.ClientCertificates; + CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy; } internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions) @@ -48,6 +49,7 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen // Server specific options. CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode; ServerCertificate = sslServerAuthenticationOptions.ServerCertificate; + CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy; } internal bool AllowRenegotiation { get; set; } @@ -64,6 +66,7 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen internal RemoteCertValidationCallback CertValidationDelegate { get; set; } internal LocalCertSelectionCallback CertSelectionDelegate { get; set; } internal ServerCertSelectionCallback ServerCertSelectionDelegate { get; set; } + internal CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } diff --git a/src/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs b/src/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs index bf68f1d6023b..bffa7d4dec8e 100644 --- a/src/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs +++ b/src/System.Net.Security/src/System/Net/Security/SslClientAuthenticationOptions.cs @@ -65,6 +65,12 @@ public SslProtocols EnabledSslProtocols get => _enabledSslProtocols; set => _enabledSslProtocols = value; } + + /// + /// Specifies cipher suites allowed to be used for TLS. + /// When set to null operating system default will be used. + /// Use extreme caution when changing this setting. + /// + public CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } - diff --git a/src/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs b/src/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs index 5b3935ba0730..614dff08c2b5 100644 --- a/src/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs +++ b/src/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs @@ -64,6 +64,13 @@ public EncryptionPolicy EncryptionPolicy _encryptionPolicy = value; } } + + /// + /// Specifies cipher suites allowed to be used for TLS. + /// When set to null operating system default will be used. + /// Use extreme caution when changing this setting. + /// + public CipherSuitesPolicy CipherSuitesPolicy { get; set; } } } diff --git a/src/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude b/src/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude index 2d510ef98e57..65dd43c51b16 100644 --- a/src/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude +++ b/src/System.Net.Security/src/System/Net/Security/TlsCipherSuiteNameParser.ttinclude @@ -135,7 +135,20 @@ internal static class EnumHelpers public static string ToFrameworkName(HashAlgorithmType val) { - return val == HashAlgorithmType.Aead ? "None" : val.ToString(); + switch (val) + { + case HashAlgorithmType.Aead: + case HashAlgorithmType.None: + return "None"; + case HashAlgorithmType.Md5: + case HashAlgorithmType.Sha1: + case HashAlgorithmType.Sha256: + case HashAlgorithmType.Sha384: + case HashAlgorithmType.Sha512: + return val.ToString(); + default: + throw new Exception($"Value `HashAlgorithmType.{val}` does not have framework corresponding name. See TlsCipherSuiteNameParser.ttinclude: ToFrameworkName."); + } } } diff --git a/src/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs b/src/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs index 4a249dd7255e..18c77ff665dc 100644 --- a/src/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs +++ b/src/System.Net.Security/tests/FunctionalTests/SslStreamNegotiatedCipherSuiteTest.cs @@ -5,11 +5,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Net.Test.Common; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; namespace System.Net.Security.Tests @@ -18,20 +20,44 @@ namespace System.Net.Security.Tests public class NegotiatedCipherSuiteTest { +#pragma warning disable CS0618 // Ssl2 and Ssl3 are obsolete + private const SslProtocols AllProtocols = + SslProtocols.Ssl2 | SslProtocols.Ssl3 | + SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; +#pragma warning restore CS0618 + + private const SslProtocols NonTls13Protocols = AllProtocols & (~SslProtocols.Tls13); + private static bool IsKnownPlatformSupportingTls13 => PlatformDetection.IsUbuntu1810OrHigher; - private static bool Tls13Supported { get; set; } = ProtocolsSupported(SslProtocols.Tls13); + private static bool CipherSuitesPolicySupported => s_cipherSuitePolicySupported.Value; + private static bool Tls13Supported { get; set; } = IsKnownPlatformSupportingTls13 || ProtocolsSupported(SslProtocols.Tls13); + private static bool CipherSuitesPolicyAndTls13Supported => Tls13Supported && CipherSuitesPolicySupported; private static HashSet s_tls13CipherSuiteLookup = new HashSet(GetTls13CipherSuites()); private static HashSet s_tls12CipherSuiteLookup = new HashSet(GetTls12CipherSuites()); private static HashSet s_tls10And11CipherSuiteLookup = new HashSet(GetTls10And11CipherSuites()); - private static Dictionary> _protocolCipherSuiteLookup = new Dictionary>() + private static Dictionary> s_protocolCipherSuiteLookup = new Dictionary>() { { SslProtocols.Tls12, s_tls12CipherSuiteLookup }, { SslProtocols.Tls11, s_tls10And11CipherSuiteLookup }, { SslProtocols.Tls, s_tls10And11CipherSuiteLookup }, }; + private static Lazy s_cipherSuitePolicySupported = new Lazy(() => + { + try + { + new CipherSuitesPolicy(Array.Empty()); + return true; + } + catch (PlatformNotSupportedException) { } + + return false; + }); + + private static IReadOnlyList SupportedNonTls13CipherSuites = GetSupportedNonTls13CipherSuites(); + [ConditionalFact(nameof(IsKnownPlatformSupportingTls13))] public void Tls13IsSupported_GetValue_ReturnsTrue() { @@ -70,7 +96,7 @@ public void NegotiatedCipherSuite_SslProtocolIsLowerThanTls13_ShouldMatchTheProt ret.Succeeded(); Assert.True( - _protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite), + s_protocolCipherSuiteLookup[protocol].Contains(ret.CipherSuite), $"`{ret.CipherSuite}` is not recognized as {protocol} cipher suite"); } @@ -84,6 +110,366 @@ public void NegotiatedCipherSuite_BeforeNegotiationStarted_ShouldThrow() } } + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowSomeCipherSuitesWithNoEncryptionOption_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var p = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256, + SupportedNonTls13CipherSuites[0]), + EncryptionPolicy = EncryptionPolicy.NoEncryption, + }; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(p, p); + ret.Failed(); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_NothingAllowed_Fails() + { + CipherSuitesPolicy csp = BuildPolicy(); + + var sp = new ConnectionParams(); + sp.CipherSuitesPolicy = csp; + + var cp = new ConnectionParams(); + cp.CipherSuitesPolicy = csp; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(sp, cp); + ret.Failed(); + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_AllowOneOnOneSideTls13_Success() + { + bool hasSucceededAtLeastOnce = false; + AllowOneOnOneSide(GetTls13CipherSuites(), + RequiredByTls13Spec, + (cs) => hasSucceededAtLeastOnce = true); + Assert.True(hasSucceededAtLeastOnce); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowTwoOnBothSidesWithSingleOverlapNonTls13_Success() + { + CheckPrereqsForNonTls13Tests(3); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[2]) + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + + ret.Succeeded(); + ret.CheckCipherSuite(SupportedNonTls13CipherSuites[1]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowTwoOnBothSidesWithNoOverlapNonTls13_Fails() + { + CheckPrereqsForNonTls13Tests(4); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[2], + SupportedNonTls13CipherSuites[3]) + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowSameTwoOnBothSidesLessPreferredIsTls13_Success() + { + CheckPrereqsForNonTls13Tests(1); + var p = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + TlsCipherSuite.TLS_AES_128_GCM_SHA256) + }; + + NegotiatedParams ret = ConnectAndGetNegotiatedParams(p, p); + ret.Succeeded(); + + // If both sides can speak TLS 1.3 they should speak it + if (Tls13Supported) + { + ret.CheckCipherSuite(TlsCipherSuite.TLS_AES_128_GCM_SHA256); + } + else + { + ret.CheckCipherSuite(SupportedNonTls13CipherSuites[0]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_TwoCipherSuitesWithAllOverlapping_Success() + { + CheckPrereqsForNonTls13Tests(2); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[0]) + }; + + for (int i = 0; i < 2; i++) + { + bool isAClient = i == 0; + NegotiatedParams ret = isAClient ? + ConnectAndGetNegotiatedParams(b, a) : + ConnectAndGetNegotiatedParams(a, b); + + ret.Succeeded(); + Assert.True(ret.CipherSuite == SupportedNonTls13CipherSuites[0] || + ret.CipherSuite == SupportedNonTls13CipherSuites[1]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_ThreeCipherSuitesWithTwoOverlapping_Success() + { + CheckPrereqsForNonTls13Tests(4); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0], + SupportedNonTls13CipherSuites[1], + SupportedNonTls13CipherSuites[2]) + }; + var b = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[3], + SupportedNonTls13CipherSuites[2], + SupportedNonTls13CipherSuites[1]) + }; + + for (int i = 0; i < 2; i++) + { + bool isAClient = i == 0; + NegotiatedParams ret = isAClient ? + ConnectAndGetNegotiatedParams(b, a) : + ConnectAndGetNegotiatedParams(a, b); + + ret.Succeeded(); + + Assert.True(ret.CipherSuite == SupportedNonTls13CipherSuites[1] || + ret.CipherSuite == SupportedNonTls13CipherSuites[2]); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_OnlyTls13CipherSuiteAllowedButChosenProtocolsDoesNotAllowIt_Fails() + { + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256), + SslProtocols = NonTls13Protocols, + }; + + var b = new ConnectionParams(); + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicyAndTls13Supported))] + public void CipherSuitesPolicy_OnlyTls13CipherSuiteAllowedOtherSideDoesNotAllowTls13_Fails() + { + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(TlsCipherSuite.TLS_AES_128_GCM_SHA256) + }; + + var b = new ConnectionParams() + { + SslProtocols = NonTls13Protocols + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_OnlyNonTls13CipherSuitesAllowedButChosenProtocolDoesNotAllowIt_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0]), + SslProtocols = SslProtocols.Tls13, + }; + + var b = new ConnectionParams(); + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_OnlyNonTls13CipherSuiteAllowedButOtherSideDoesNotAllowIt_Fails() + { + CheckPrereqsForNonTls13Tests(1); + var a = new ConnectionParams() + { + CipherSuitesPolicy = BuildPolicy(SupportedNonTls13CipherSuites[0]) + }; + + var b = new ConnectionParams() + { + SslProtocols = SslProtocols.Tls13 + }; + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(a, b) : + ConnectAndGetNegotiatedParams(b, a); + ret.Failed(); + } + } + + [Fact] + public void CipherSuitesPolicy_CtorWithNull_Fails() + { + Assert.Throws(() => new CipherSuitesPolicy(null)); + } + + [ConditionalFact(nameof(CipherSuitesPolicySupported))] + public void CipherSuitesPolicy_AllowedCipherSuitesIncludesSubsetOfInput_Success() + { + TlsCipherSuite[] allCipherSuites = (TlsCipherSuite[])Enum.GetValues(typeof(TlsCipherSuite)); + var r = new Random(123); + int[] numOfCipherSuites = new int[] { 0, 1, 2, 5, 10, 15, 30 }; + + foreach (int n in numOfCipherSuites) + { + HashSet cipherSuites = PickRandomValues(allCipherSuites, n, r); + var csp = new CipherSuitesPolicy(cipherSuites); + Assert.NotNull(csp.AllowedCipherSuites); + Assert.InRange(csp.AllowedCipherSuites.Count(), 0, n); + + foreach (var cs in csp.AllowedCipherSuites) + { + Assert.True(cipherSuites.Contains(cs)); + } + } + } + + private HashSet PickRandomValues(TlsCipherSuite[] all, int n, Random r) + { + var ret = new HashSet(); + + while (ret.Count != n) + { + ret.Add(all[r.Next() % n]); + } + + return ret; + } + + private static void AllowOneOnOneSide(IEnumerable cipherSuites, + Predicate mustSucceed, + Action cipherSuitePicked = null) + { + foreach (TlsCipherSuite cs in cipherSuites) + { + CipherSuitesPolicy csp = BuildPolicy(cs); + + var paramsA = new ConnectionParams() + { + CipherSuitesPolicy = csp, + }; + + var paramsB = new ConnectionParams(); + int score = 0; // 1 for success 0 for fail. Sum should be even + + for (int i = 0; i < 2; i++) + { + NegotiatedParams ret = i == 0 ? + ConnectAndGetNegotiatedParams(paramsA, paramsB) : + ConnectAndGetNegotiatedParams(paramsB, paramsA); + + score += ret.HasSucceeded ? 1 : 0; + if (mustSucceed(cs) || ret.HasSucceeded) + { + // we do not always guarantee success but if it succeeds it + // must use the picked cipher suite + ret.Succeeded(); + ret.CheckCipherSuite(cs); + + if (cipherSuitePicked != null && i == 0) + { + cipherSuitePicked(cs); + } + } + } + + // we should either get 2 successes or 2 failures + Assert.True(score % 2 == 0); + } + } + + private static void CheckPrereqsForNonTls13Tests(int minCipherSuites) + { + if (SupportedNonTls13CipherSuites.Count < minCipherSuites) + { + // We do not want to accidentally make the tests pass due to the bug in the code + // This situation is rather unexpected but can happen on i.e. Alpine + // Make sure at least some tests run. + + if (Tls13Supported) + { + throw new SkipTestException($"Test requires that at least {minCipherSuites} non TLS 1.3 cipher suites are supported."); + } + else + { + throw new Exception($"Less than {minCipherSuites} cipher suites are supported: {string.Join(", ", SupportedNonTls13CipherSuites)}"); + } + } + } + private static bool ProtocolsSupported(SslProtocols protocols) { var defaultParams = new ConnectionParams(); @@ -165,6 +551,42 @@ private static IEnumerable GetTls10And11CipherSuites() yield return TlsCipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; } + private static IEnumerable GetNonTls13CipherSuites() + { + var tls13cs = new HashSet(GetTls13CipherSuites()); + foreach (TlsCipherSuite cs in typeof(TlsCipherSuite).GetEnumValues()) + { + if (!tls13cs.Contains(cs)) + { + yield return cs; + } + } + } + + private static IReadOnlyList GetSupportedNonTls13CipherSuites() + { + // This function is used to initialize static property. + // We do not want skipped tests to fail because of that. + if (!CipherSuitesPolicySupported) + return null; + + var ret = new List(); + AllowOneOnOneSide(GetNonTls13CipherSuites(), (cs) => false, (cs) => ret.Add(cs)); + + return ret; + } + + private static bool RequiredByTls13Spec(TlsCipherSuite cs) + { + // per spec only one MUST be implemented + return cs == TlsCipherSuite.TLS_AES_128_GCM_SHA256; + } + + private static CipherSuitesPolicy BuildPolicy(params TlsCipherSuite[] cipherSuites) + { + return new CipherSuitesPolicy(cipherSuites); + } + private static async Task WaitForSecureConnection(VirtualNetwork connection, Func server, Func client) { Task serverTask = null; @@ -253,10 +675,12 @@ private static NegotiatedParams ConnectAndGetNegotiatedParams(ConnectionParams s serverOptions.ServerCertificate = Configuration.Certificates.GetSelfSignedServerCertificate(); serverOptions.EncryptionPolicy = serverParams.EncryptionPolicy; serverOptions.EnabledSslProtocols = serverParams.SslProtocols; + serverOptions.CipherSuitesPolicy = serverParams.CipherSuitesPolicy; var clientOptions = new SslClientAuthenticationOptions(); clientOptions.EncryptionPolicy = clientParams.EncryptionPolicy; clientOptions.EnabledSslProtocols = clientParams.SslProtocols; + clientOptions.CipherSuitesPolicy = clientParams.CipherSuitesPolicy; clientOptions.TargetHost = "test"; clientOptions.RemoteCertificateValidationCallback = new RemoteCertificateValidationCallback((object sender, @@ -293,6 +717,7 @@ private static NegotiatedParams ConnectAndGetNegotiatedParams(ConnectionParams s private class ConnectionParams { + public CipherSuitesPolicy CipherSuitesPolicy = null; public EncryptionPolicy EncryptionPolicy = EncryptionPolicy.RequireEncryption; public SslProtocols SslProtocols = SslProtocols.None; } diff --git a/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj b/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj index 6d7c639e03b9..8faa93ca6b92 100644 --- a/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj +++ b/src/System.Net.Security/tests/FunctionalTests/System.Net.Security.Tests.csproj @@ -148,6 +148,6 @@ - + diff --git a/src/System.Net.Sockets/System.Net.Sockets.sln b/src/System.Net.Sockets/System.Net.Sockets.sln index 8a3657e31f29..411d0fb0b4c1 100644 --- a/src/System.Net.Sockets/System.Net.Sockets.sln +++ b/src/System.Net.Sockets/System.Net.Sockets.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27213.1 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28725.219 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Net.Sockets.Tests", "tests\FunctionalTests\System.Net.Sockets.Tests.csproj", "{8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}" ProjectSection(ProjectDependencies) = postProject @@ -31,10 +31,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU - {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU - {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU - {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU + {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.ActiveCfg = netcoreapp-Unix-Debug|Any CPU + {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Debug|Any CPU.Build.0 = netcoreapp-Unix-Debug|Any CPU + {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.ActiveCfg = netcoreapp-Unix-Release|Any CPU + {8CBA022C-635F-4C8D-9D29-CD8AAC68C8E6}.Release|Any CPU.Build.0 = netcoreapp-Unix-Release|Any CPU {BB5C85AD-C51A-4903-80E9-6F6E1AC1AD34}.Debug|Any CPU.ActiveCfg = netstandard-Windows_NT-Debug|Any CPU {BB5C85AD-C51A-4903-80E9-6F6E1AC1AD34}.Debug|Any CPU.Build.0 = netstandard-Windows_NT-Debug|Any CPU {BB5C85AD-C51A-4903-80E9-6F6E1AC1AD34}.Release|Any CPU.ActiveCfg = netstandard-Windows_NT-Release|Any CPU diff --git a/src/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/System.Net.Sockets/src/System.Net.Sockets.csproj index 42b29461aaad..240a0df9e87a 100644 --- a/src/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -232,6 +232,9 @@ Interop\Windows\WinSock\WSABuffer.cs + + Common\Interop\Windows\Interop.CancelIoEx.cs + Interop\Windows\Kernel32\Interop.SetFileCompletionNotificationModes.cs @@ -378,6 +381,7 @@ + diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs b/src/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs index 46d20a71104c..15d4ac617949 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/NetworkStream.cs @@ -2,10 +2,7 @@ // 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.Buffers; -using System.Diagnostics; using System.IO; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -846,7 +843,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo return _streamSocket.SendAsyncForNetworkStream( buffer, SocketFlags.None, - cancellationToken: cancellationToken); + cancellationToken); } catch (Exception exception) when (!(exception is OutOfMemoryException)) { diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs b/src/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs index 69349d0cba2b..a30c2a10b00e 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs @@ -201,7 +201,7 @@ internal ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlag saea.SetBuffer(buffer); saea.SocketFlags = socketFlags; saea.WrapExceptionsInIOExceptions = fromNetworkStream; - return saea.ReceiveAsync(this); + return saea.ReceiveAsync(this, cancellationToken); } else { @@ -350,7 +350,7 @@ internal ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socke saea.SetBuffer(MemoryMarshal.AsMemory(buffer)); saea.SocketFlags = socketFlags; saea.WrapExceptionsInIOExceptions = false; - return saea.SendAsync(this); + return saea.SendAsync(this, cancellationToken); } else { @@ -828,6 +828,8 @@ internal sealed class AwaitableSocketAsyncEventArgs : SocketAsyncEventArgs, IVal /// it's already being reused by someone else. /// private short _token; + /// The cancellation token used for the current operation. + private CancellationToken _cancellationToken; /// Initializes the event args. public AwaitableSocketAsyncEventArgs() : @@ -842,6 +844,7 @@ public bool Reserve() => private void Release() { + _cancellationToken = default; _token++; Volatile.Write(ref _continuation, s_availableSentinel); } @@ -882,12 +885,13 @@ protected override void OnCompleted(SocketAsyncEventArgs _) /// Initiates a receive operation on the associated socket. /// This instance. - public ValueTask ReceiveAsync(Socket socket) + public ValueTask ReceiveAsync(Socket socket, CancellationToken cancellationToken) { Debug.Assert(Volatile.Read(ref _continuation) == null, $"Expected null continuation to indicate reserved for use"); - if (socket.ReceiveAsync(this)) + if (socket.ReceiveAsync(this, cancellationToken)) { + _cancellationToken = cancellationToken; return new ValueTask(this, _token); } @@ -903,12 +907,13 @@ public ValueTask ReceiveAsync(Socket socket) /// Initiates a send operation on the associated socket. /// This instance. - public ValueTask SendAsync(Socket socket) + public ValueTask SendAsync(Socket socket, CancellationToken cancellationToken) { Debug.Assert(Volatile.Read(ref _continuation) == null, $"Expected null continuation to indicate reserved for use"); - if (socket.SendAsync(this)) + if (socket.SendAsync(this, cancellationToken)) { + _cancellationToken = cancellationToken; return new ValueTask(this, _token); } @@ -1059,12 +1064,13 @@ public int GetResult(short token) SocketError error = SocketError; int bytes = BytesTransferred; + CancellationToken cancellationToken = _cancellationToken; Release(); if (error != SocketError.Success) { - ThrowException(error); + ThrowException(error, cancellationToken); } return bytes; } @@ -1077,12 +1083,13 @@ void IValueTaskSource.GetResult(short token) } SocketError error = SocketError; + CancellationToken cancellationToken = _cancellationToken; Release(); if (error != SocketError.Success) { - ThrowException(error); + ThrowException(error, cancellationToken); } } @@ -1090,7 +1097,15 @@ void IValueTaskSource.GetResult(short token) private void ThrowMultipleContinuationsException() => throw new InvalidOperationException(SR.InvalidOperation_MultipleContinuations); - private void ThrowException(SocketError error) => throw CreateException(error); + private void ThrowException(SocketError error, CancellationToken cancellationToken) + { + if (error == SocketError.OperationAborted) + { + cancellationToken.ThrowIfCancellationRequested(); + } + + throw CreateException(error); + } private Exception CreateException(SocketError error) { diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs b/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs index 49463a3dd9e9..7d04f748ece7 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/Socket.cs @@ -4038,7 +4038,9 @@ public bool DisconnectAsync(SocketAsyncEventArgs e) return retval; } - public bool ReceiveAsync(SocketAsyncEventArgs e) + public bool ReceiveAsync(SocketAsyncEventArgs e) => ReceiveAsync(e, default(CancellationToken)); + + private bool ReceiveAsync(SocketAsyncEventArgs e, CancellationToken cancellationToken) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, e); @@ -4057,7 +4059,7 @@ public bool ReceiveAsync(SocketAsyncEventArgs e) SocketError socketError; try { - socketError = e.DoOperationReceive(_handle); + socketError = e.DoOperationReceive(_handle, cancellationToken); } catch { @@ -4179,7 +4181,9 @@ public bool ReceiveMessageFromAsync(SocketAsyncEventArgs e) return pending; } - public bool SendAsync(SocketAsyncEventArgs e) + public bool SendAsync(SocketAsyncEventArgs e) => SendAsync(e, default(CancellationToken)); + + private bool SendAsync(SocketAsyncEventArgs e, CancellationToken cancellationToken) { if (NetEventSource.IsEnabled) NetEventSource.Enter(this, e); @@ -4198,7 +4202,7 @@ public bool SendAsync(SocketAsyncEventArgs e) SocketError socketError; try { - socketError = e.DoOperationSend(_handle); + socketError = e.DoOperationSend(_handle, cancellationToken); } catch { diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs index a2b7ecef9ffe..3ed34d29bea6 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncContext.Unix.cs @@ -130,6 +130,7 @@ private enum State public SocketError ErrorCode; public byte[] SocketAddress; public int SocketAddressLen; + public CancellationTokenRegistration CancellationRegistration; public ManualResetEventSlim Event { @@ -195,6 +196,11 @@ public bool TryCancel() { Trace("Enter"); + // We're already canceling, so we don't need to still be hooked up to listen to cancellation. + // The cancellation request could also be caused by something other than the token, so it's + // important we clean it up, regardless. + CancellationRegistration.Dispose(); + // Try to transition from Waiting to Cancelled var spinWait = new SpinWait(); bool keepWaiting = true; @@ -739,7 +745,7 @@ public bool IsReady(SocketAsyncContext context, out int observedSequenceNumber) } // Return true for pending, false for completed synchronously (including failure and abort) - public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation, int observedSequenceNumber) + public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation, int observedSequenceNumber, CancellationToken cancellationToken = default) { Trace(context, $"Enter"); @@ -781,8 +787,16 @@ public bool StartAsyncOperation(SocketAsyncContext context, TOperation operation } _tail = operation; - Trace(context, $"Leave, enqueued {IdOf(operation)}"); + + // Now that the object is enqueued, hook up cancellation. + // Note that it's possible the call to register itself could + // call TryCancel, so we do this after the op is fully enqueued. + if (cancellationToken.CanBeCanceled) + { + operation.CancellationRegistration = cancellationToken.UnsafeRegister(s => ((TOperation)s).TryCancel(), operation); + } + return true; case QueueState.Stopped: @@ -866,7 +880,12 @@ internal void ProcessAsyncOperation(TOperation op) { // At this point, the operation has completed and it's no longer // in the queue / no one else has a reference to it. We can invoke - // the callback and let it pool the object if appropriate. + // the callback and let it pool the object if appropriate. This is + // also a good time to unregister from cancellation; we must do + // so before the object is returned to the pool (or else a cancellation + // request for a previous operation could affect a subsequent one) + // and here we know the operation has completed. + op.CancellationRegistration.Dispose(); op.InvokeCallback(allowPooling: true); } } @@ -1410,10 +1429,10 @@ public SocketError Receive(Span buffer, ref SocketFlags flags, int timeout return ReceiveFrom(buffer, ref flags, null, ref socketAddressLen, timeout, out bytesReceived); } - public SocketError ReceiveAsync(Memory buffer, SocketFlags flags, out int bytesReceived, out SocketFlags receivedFlags, Action callback) + public SocketError ReceiveAsync(Memory buffer, SocketFlags flags, out int bytesReceived, out SocketFlags receivedFlags, Action callback, CancellationToken cancellationToken) { int socketAddressLen = 0; - return ReceiveFromAsync(buffer, flags, null, ref socketAddressLen, out bytesReceived, out receivedFlags, callback); + return ReceiveFromAsync(buffer, flags, null, ref socketAddressLen, out bytesReceived, out receivedFlags, callback, cancellationToken); } public SocketError ReceiveFrom(Memory buffer, ref SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, int timeout, out int bytesReceived) @@ -1478,7 +1497,7 @@ public unsafe SocketError ReceiveFrom(Span buffer, ref SocketFlags flags, } } - public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, Action callback) + public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, Action callback, CancellationToken cancellationToken = default) { SetNonBlocking(); @@ -1497,7 +1516,7 @@ public SocketError ReceiveFromAsync(Memory buffer, SocketFlags flags, byt operation.SocketAddress = socketAddress; operation.SocketAddressLen = socketAddressLen; - if (!_receiveQueue.StartAsyncOperation(this, operation, observedSequenceNumber)) + if (!_receiveQueue.StartAsyncOperation(this, operation, observedSequenceNumber, cancellationToken)) { receivedFlags = operation.ReceivedFlags; bytesReceived = operation.BytesTransferred; @@ -1673,10 +1692,10 @@ public SocketError Send(byte[] buffer, int offset, int count, SocketFlags flags, return SendTo(buffer, offset, count, flags, null, 0, timeout, out bytesSent); } - public SocketError SendAsync(Memory buffer, int offset, int count, SocketFlags flags, out int bytesSent, Action callback) + public SocketError SendAsync(Memory buffer, int offset, int count, SocketFlags flags, out int bytesSent, Action callback, CancellationToken cancellationToken) { int socketAddressLen = 0; - return SendToAsync(buffer, offset, count, flags, null, ref socketAddressLen, out bytesSent, callback); + return SendToAsync(buffer, offset, count, flags, null, ref socketAddressLen, out bytesSent, callback, cancellationToken); } public SocketError SendTo(byte[] buffer, int offset, int count, SocketFlags flags, byte[] socketAddress, int socketAddressLen, int timeout, out int bytesSent) @@ -1745,7 +1764,7 @@ public unsafe SocketError SendTo(ReadOnlySpan buffer, SocketFlags flags, b } } - public SocketError SendToAsync(Memory buffer, int offset, int count, SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesSent, Action callback) + public SocketError SendToAsync(Memory buffer, int offset, int count, SocketFlags flags, byte[] socketAddress, ref int socketAddressLen, out int bytesSent, Action callback, CancellationToken cancellationToken = default) { SetNonBlocking(); @@ -1768,7 +1787,7 @@ public SocketError SendToAsync(Memory buffer, int offset, int count, Socke operation.SocketAddressLen = socketAddressLen; operation.BytesTransferred = bytesSent; - if (!_sendQueue.StartAsyncOperation(this, operation, observedSequenceNumber)) + if (!_sendQueue.StartAsyncOperation(this, operation, observedSequenceNumber, cancellationToken)) { bytesSent = operation.BytesTransferred; errorCode = operation.ErrorCode; diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs index 20281ac752b4..c893e798819f 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEngine.Unix.cs @@ -3,7 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; @@ -124,9 +124,8 @@ public bool TryRegister(SafeSocketHandle socket, out Interop.Error error) // // Maps handle values to SocketAsyncContext instances. - // Must be accessed under s_lock. // - private readonly Dictionary _handleToContextMap = new Dictionary(); + private readonly ConcurrentDictionary _handleToContextMap = new ConcurrentDictionary(); // // True if we've reached the handle value limit for this event port, and thus must allocate a new event port @@ -193,7 +192,7 @@ private IntPtr AllocateHandle(SocketAsyncContext context) Debug.Assert(!IsFull, "Expected !IsFull"); IntPtr handle = _nextHandle; - _handleToContextMap.Add(handle, context); + _handleToContextMap.TryAdd(handle, context); _nextHandle = IntPtr.Add(_nextHandle, 1); _outstandingHandles = IntPtr.Add(_outstandingHandles, 1); @@ -210,7 +209,7 @@ private void FreeHandle(IntPtr handle) lock (s_lock) { - if (_handleToContextMap.Remove(handle)) + if (_handleToContextMap.TryRemove(handle, out _)) { _outstandingHandles = IntPtr.Subtract(_outstandingHandles, 1); Debug.Assert(_outstandingHandles.ToInt64() >= 0, $"Unexpected _outstandingHandles: {_outstandingHandles}"); @@ -235,18 +234,6 @@ private void FreeHandle(IntPtr handle) } } - private SocketAsyncContext GetContextFromHandle(IntPtr handle) - { - Debug.Assert(handle != ShutdownHandle, $"Expected handle != ShutdownHandle: {handle}"); - Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); - lock (s_lock) - { - SocketAsyncContext context; - _handleToContextMap.TryGetValue(handle, out context); - return context; - } - } - private SocketAsyncEngine() { _port = (IntPtr)(-1); @@ -339,7 +326,8 @@ private void EventLoop() } else { - SocketAsyncContext context = GetContextFromHandle(handle); + Debug.Assert(handle.ToInt64() < MaxHandles.ToInt64(), $"Unexpected values: handle={handle}, MaxHandles={MaxHandles}"); + _handleToContextMap.TryGetValue(handle, out SocketAsyncContext context); if (context != null) { context.HandleEvents(_buffer[i].Events); diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs index 295d471ca9c3..eb9f1119992a 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Unix.cs @@ -4,6 +4,8 @@ using System.Diagnostics; using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace System.Net.Sockets { @@ -127,7 +129,7 @@ private void CompleteTransferOperation(int bytesTransferred, byte[] socketAddres _receivedFlags = receivedFlags; } - internal unsafe SocketError DoOperationReceive(SafeSocketHandle handle) + internal unsafe SocketError DoOperationReceive(SafeSocketHandle handle, CancellationToken cancellationToken) { _receivedFlags = System.Net.Sockets.SocketFlags.None; _socketAddressSize = 0; @@ -137,7 +139,7 @@ internal unsafe SocketError DoOperationReceive(SafeSocketHandle handle) SocketError errorCode; if (_bufferList == null) { - errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, out flags, TransferCompletionCallback); + errorCode = handle.AsyncContext.ReceiveAsync(_buffer.Slice(_offset, _count), _socketFlags, out bytesReceived, out flags, TransferCompletionCallback, cancellationToken); } else { @@ -219,7 +221,7 @@ internal unsafe SocketError DoOperationReceiveMessageFrom(Socket socket, SafeSoc return socketError; } - internal unsafe SocketError DoOperationSend(SafeSocketHandle handle) + internal unsafe SocketError DoOperationSend(SafeSocketHandle handle, CancellationToken cancellationToken) { _receivedFlags = System.Net.Sockets.SocketFlags.None; _socketAddressSize = 0; @@ -228,7 +230,7 @@ internal unsafe SocketError DoOperationSend(SafeSocketHandle handle) SocketError errorCode; if (_bufferList == null) { - errorCode = handle.AsyncContext.SendAsync(_buffer, _offset, _count, _socketFlags, out bytesSent, TransferCompletionCallback); + errorCode = handle.AsyncContext.SendAsync(_buffer, _offset, _count, _socketFlags, out bytesSent, TransferCompletionCallback, cancellationToken); } else { diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs index d8cbc4778ec6..bb95bd11c653 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketAsyncEventArgs.Windows.cs @@ -43,6 +43,10 @@ private enum SingleBufferHandleState : byte { None, InProcess, Set } private PreAllocatedOverlapped _preAllocatedOverlapped; private readonly StrongBox _strongThisRef = new StrongBox(); // state for _preAllocatedOverlapped; .Value set to this while operations in flight + // Cancellation support + private CancellationTokenRegistration _registrationToCancelPendingIO; + private unsafe NativeOverlapped* _pendingOverlappedForCancellation; + private PinState _pinState; private enum PinState : byte { None = 0, MultipleBuffer, SendPackets } @@ -94,6 +98,37 @@ private unsafe void FreeNativeOverlapped(NativeOverlapped* overlapped) _currentSocket.SafeHandle.IOCPBoundHandle.FreeNativeOverlapped(overlapped); } + private unsafe void RegisterToCancelPendingIO(NativeOverlapped* overlapped, CancellationToken cancellationToken) + { + Debug.Assert(_singleBufferHandleState == SingleBufferHandleState.InProcess); + Debug.Assert(_pendingOverlappedForCancellation == null); + _pendingOverlappedForCancellation = overlapped; + _registrationToCancelPendingIO = cancellationToken.UnsafeRegister(s => + { + // Try to cancel the I/O. We ignore the return value (other than for logging), as cancellation + // is opportunistic and we don't want to fail the operation because we couldn't cancel it. + var thisRef = (SocketAsyncEventArgs)s; + SafeSocketHandle handle = thisRef._currentSocket.SafeHandle; + if (!handle.IsClosed) + { + try + { + bool canceled = Interop.Kernel32.CancelIoEx(handle, thisRef._pendingOverlappedForCancellation); + if (NetEventSource.IsEnabled) + { + NetEventSource.Info(thisRef, canceled ? + "Socket operation canceled." : + $"CancelIoEx failed with error '{Marshal.GetLastWin32Error()}'."); + } + } + catch (ObjectDisposedException) + { + // Ignore errors resulting from the SafeHandle being closed concurrently. + } + } + }, this); + } + partial void StartOperationCommonCore() { // Store the reference to this instance so that it's kept alive by the preallocated @@ -152,8 +187,9 @@ private unsafe SocketError ProcessIOCPResult(bool success, int bytesTransferred, /// The result status of the operation, as returned from the API call. /// The number of bytes transferred, if the operation completed synchronously and successfully. /// The overlapped to be freed if the operation completed synchronously. + /// The cancellation token to use to cancel the operation. /// The result status of the operation. - private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError socketError, int bytesTransferred, NativeOverlapped* overlapped) + private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError socketError, int bytesTransferred, NativeOverlapped* overlapped, CancellationToken cancellationToken = default) { // Note: We need to dispose of the overlapped iff the operation completed synchronously, // and if we do, we must do so before we mark the operation as completed. @@ -194,6 +230,7 @@ private unsafe SocketError ProcessIOCPResultWithSingleBufferHandle(SocketError s // Return pending and we will continue in the completion port callback. if (_singleBufferHandleState == SingleBufferHandleState.InProcess) { + RegisterToCancelPendingIO(overlapped, cancellationToken);// must happen before we change state to Set to avoid race conditions _singleBufferHandle = _buffer.Pin(); _singleBufferHandleState = SingleBufferHandleState.Set; } @@ -288,11 +325,11 @@ internal unsafe SocketError DoOperationDisconnect(Socket socket, SafeSocketHandl } } - internal SocketError DoOperationReceive(SafeSocketHandle handle) => _bufferList == null ? - DoOperationReceiveSingleBuffer(handle) : + internal SocketError DoOperationReceive(SafeSocketHandle handle, CancellationToken cancellationToken) => _bufferList == null ? + DoOperationReceiveSingleBuffer(handle, cancellationToken) : DoOperationReceiveMultiBuffer(handle); - internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeSocketHandle handle) + internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeSocketHandle handle, CancellationToken cancellationToken) { fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span)) { @@ -305,16 +342,15 @@ internal unsafe SocketError DoOperationReceiveSingleBuffer(SafeSocketHandle hand SocketFlags flags = _socketFlags; SocketError socketError = Interop.Winsock.WSARecv( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, ref wsaBuffer, 1, out int bytesTransferred, ref flags, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress - return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped); + return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped, cancellationToken); } catch { @@ -332,14 +368,13 @@ internal unsafe SocketError DoOperationReceiveMultiBuffer(SafeSocketHandle handl { SocketFlags flags = _socketFlags; SocketError socketError = Interop.Winsock.WSARecv( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, _wsaBufferArray, _bufferListInternal.Count, out int bytesTransferred, ref flags, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped); } @@ -377,7 +412,7 @@ internal unsafe SocketError DoOperationReceiveFromSingleBuffer(SafeSocketHandle SocketFlags flags = _socketFlags; SocketError socketError = Interop.Winsock.WSARecvFrom( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, ref wsaBuffer, 1, out int bytesTransferred, @@ -386,7 +421,6 @@ internal unsafe SocketError DoOperationReceiveFromSingleBuffer(SafeSocketHandle PtrSocketAddressBufferSize, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped); } @@ -406,7 +440,7 @@ internal unsafe SocketError DoOperationReceiveFromMultiBuffer(SafeSocketHandle h { SocketFlags flags = _socketFlags; SocketError socketError = Interop.Winsock.WSARecvFrom( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, _wsaBufferArray, _bufferListInternal.Count, out int bytesTransferred, @@ -415,7 +449,6 @@ internal unsafe SocketError DoOperationReceiveFromMultiBuffer(SafeSocketHandle h PtrSocketAddressBufferSize, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped); } @@ -556,11 +589,11 @@ internal unsafe SocketError DoOperationReceiveMessageFrom(Socket socket, SafeSoc } } - internal unsafe SocketError DoOperationSend(SafeSocketHandle handle) => _bufferList == null ? - DoOperationSendSingleBuffer(handle) : + internal unsafe SocketError DoOperationSend(SafeSocketHandle handle, CancellationToken cancellationToken) => _bufferList == null ? + DoOperationSendSingleBuffer(handle, cancellationToken) : DoOperationSendMultiBuffer(handle); - internal unsafe SocketError DoOperationSendSingleBuffer(SafeSocketHandle handle) + internal unsafe SocketError DoOperationSendSingleBuffer(SafeSocketHandle handle, CancellationToken cancellationToken) { fixed (byte* bufferPtr = &MemoryMarshal.GetReference(_buffer.Span)) { @@ -572,16 +605,15 @@ internal unsafe SocketError DoOperationSendSingleBuffer(SafeSocketHandle handle) var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) }; SocketError socketError = Interop.Winsock.WSASend( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, ref wsaBuffer, 1, out int bytesTransferred, _socketFlags, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress - return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped); + return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped, cancellationToken); } catch { @@ -598,14 +630,13 @@ internal unsafe SocketError DoOperationSendMultiBuffer(SafeSocketHandle handle) try { SocketError socketError = Interop.Winsock.WSASend( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, _wsaBufferArray, _bufferListInternal.Count, out int bytesTransferred, _socketFlags, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped); } @@ -742,7 +773,7 @@ internal unsafe SocketError DoOperationSendToSingleBuffer(SafeSocketHandle handl var wsaBuffer = new WSABuffer { Length = _count, Pointer = (IntPtr)(bufferPtr + _offset) }; SocketError socketError = Interop.Winsock.WSASendTo( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, ref wsaBuffer, 1, out int bytesTransferred, @@ -751,7 +782,6 @@ internal unsafe SocketError DoOperationSendToSingleBuffer(SafeSocketHandle handl _socketAddress.Size, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResultWithSingleBufferHandle(socketError, bytesTransferred, overlapped); } @@ -770,7 +800,7 @@ internal unsafe SocketError DoOperationSendToMultiBuffer(SafeSocketHandle handle try { SocketError socketError = Interop.Winsock.WSASendTo( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, _wsaBufferArray, _bufferListInternal.Count, out int bytesTransferred, @@ -779,7 +809,6 @@ internal unsafe SocketError DoOperationSendToMultiBuffer(SafeSocketHandle handle _socketAddress.Size, overlapped, IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return ProcessIOCPResult(socketError == SocketError.Success, bytesTransferred, overlapped); } @@ -1151,12 +1180,25 @@ private void CompleteCore() void CompleteCoreSpin() // separate out to help inline the fast path { + // The operation could complete so quickly that it races with the code + // initiating it. Wait until that initiation code has completed before + // we try to undo the state it configures. var sw = new SpinWait(); while (_singleBufferHandleState == SingleBufferHandleState.InProcess) { sw.SpinOnce(); } + // Remove any cancellation registration. First dispose the registration + // to ensure that cancellation will either never fine or will have completed + // firing before we continue. Only then can we safely null out the overlapped. + _registrationToCancelPendingIO.Dispose(); + unsafe + { + _pendingOverlappedForCancellation = null; + } + + // Release any GC handles. if (_singleBufferHandleState == SingleBufferHandleState.Set) { _singleBufferHandleState = SingleBufferHandleState.None; diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs index f882aa7ec355..8429207cb917 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; namespace System.Net.Sockets @@ -84,11 +85,10 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, Sp }; errno = Interop.Sys.ReceiveMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &received); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress receivedFlags = messageHeader.Flags; sockAddrLen = messageHeader.SocketAddressLen; @@ -125,11 +125,10 @@ private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, ReadO long bytesSent = 0; errno = Interop.Sys.SendMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &bytesSent); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress sent = checked((int)bytesSent); } @@ -188,11 +187,10 @@ private static unsafe int Send(SafeSocketHandle socket, SocketFlags flags, IList long bytesSent = 0; errno = Interop.Sys.SendMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &bytesSent); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress sent = checked((int)bytesSent); } @@ -311,11 +309,10 @@ private static unsafe int Receive(SafeSocketHandle socket, SocketFlags flags, IL }; errno = Interop.Sys.ReceiveMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &received); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress receivedFlags = messageHeader.Flags; sockAddrLen = messageHeader.SocketAddressLen; @@ -372,11 +369,10 @@ private static unsafe int ReceiveMessageFrom(SafeSocketHandle socket, SocketFlag }; errno = Interop.Sys.ReceiveMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &received); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress receivedFlags = messageHeader.Flags; sockAddrLen = messageHeader.SocketAddressLen; @@ -435,11 +431,10 @@ private static unsafe int ReceiveMessageFrom( long received = 0; errno = Interop.Sys.ReceiveMessage( - socket.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + socket, &messageHeader, flags, &received); - GC.KeepAlive(socket); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress receivedFlags = messageHeader.Flags; int sockAddrLen = messageHeader.SocketAddressLen; @@ -1554,7 +1549,7 @@ public static SocketError ConnectAsync(Socket socket, SafeSocketHandle handle, b public static SocketError SendAsync(SafeSocketHandle handle, byte[] buffer, int offset, int count, SocketFlags socketFlags, OverlappedAsyncResult asyncResult) { int bytesSent; - SocketError socketError = handle.AsyncContext.SendAsync(buffer, offset, count, socketFlags, out bytesSent, asyncResult.CompletionCallback); + SocketError socketError = handle.AsyncContext.SendAsync(buffer, offset, count, socketFlags, out bytesSent, asyncResult.CompletionCallback, CancellationToken.None); if (socketError == SocketError.Success) { asyncResult.CompletionCallback(bytesSent, null, 0, SocketFlags.None, SocketError.Success); @@ -1678,7 +1673,7 @@ public static SocketError ReceiveAsync(SafeSocketHandle handle, byte[] buffer, i { int bytesReceived; SocketFlags receivedFlags; - SocketError socketError = handle.AsyncContext.ReceiveAsync(new Memory(buffer, offset, count), socketFlags, out bytesReceived, out receivedFlags, asyncResult.CompletionCallback); + SocketError socketError = handle.AsyncContext.ReceiveAsync(new Memory(buffer, offset, count), socketFlags, out bytesReceived, out receivedFlags, asyncResult.CompletionCallback, CancellationToken.None); if (socketError == SocketError.Success) { asyncResult.CompletionCallback(bytesReceived, null, 0, receivedFlags, SocketError.Success); diff --git a/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs b/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs index ee0c67974d22..2b0b9f4c2526 100644 --- a/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs +++ b/src/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Windows.cs @@ -919,14 +919,13 @@ public static unsafe SocketError SendAsync(SafeSocketHandle handle, byte[] buffe { int bytesTransferred; SocketError errorCode = Interop.Winsock.WSASend( - handle.DangerousGetHandle(), // to minimize chances of handle recycling from misuse, this should use DangerousAddRef/Release, but it adds too much overhead + handle, ref asyncResult._singleBuffer, 1, // There is only ever 1 buffer being sent. out bytesTransferred, socketFlags, asyncResult.DangerousOverlappedPointer, // SafeHandle was just created in SetUnmanagedStructures IntPtr.Zero); - GC.KeepAlive(handle); // small extra safe guard against handle getting collected/finalized while P/Invoke in progress return asyncResult.ProcessOverlappedResult(errorCode == SocketError.Success, bytesTransferred); } @@ -945,14 +944,13 @@ public static unsafe SocketError SendAsync(SafeSocketHandle handle, IList }); } + [Fact] + public async Task ReadAsync_CancelPendingRead_DoesntImpactSubsequentReads() + { + await RunWithConnectedNetworkStreamsAsync(async (server, client) => + { + await Assert.ThrowsAnyAsync(() => client.ReadAsync(new byte[1], 0, 1, new CancellationToken(true))); + await Assert.ThrowsAnyAsync(async () => { await client.ReadAsync(new Memory(new byte[1]), new CancellationToken(true)); }); + + CancellationTokenSource cts = new CancellationTokenSource(); + Task t = client.ReadAsync(new byte[1], 0, 1, cts.Token); + cts.Cancel(); + await Assert.ThrowsAnyAsync(() => t); + + cts = new CancellationTokenSource(); + ValueTask vt = client.ReadAsync(new Memory(new byte[1]), cts.Token); + cts.Cancel(); + await Assert.ThrowsAnyAsync(async () => await vt); + + byte[] buffer = new byte[1]; + vt = client.ReadAsync(new Memory(buffer)); + Assert.False(vt.IsCompleted); + await server.WriteAsync(new ReadOnlyMemory(new byte[1] { 42 })); + Assert.Equal(1, await vt); + Assert.Equal(42, buffer[0]); + }); + } + private sealed class CustomSynchronizationContext : SynchronizationContext { public override void Post(SendOrPostCallback d, object state) diff --git a/src/System.Net.Sockets/tests/FunctionalTests/SendReceive.netcoreapp.cs b/src/System.Net.Sockets/tests/FunctionalTests/SendReceive.netcoreapp.cs index 08322a124682..c2435589bf6c 100644 --- a/src/System.Net.Sockets/tests/FunctionalTests/SendReceive.netcoreapp.cs +++ b/src/System.Net.Sockets/tests/FunctionalTests/SendReceive.netcoreapp.cs @@ -44,6 +44,72 @@ public async Task Precanceled_Throws() } } + [Fact] + public async Task CanceledDuringOperation_Throws() + { + using (var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + listener.BindToAnonymousPort(IPAddress.Loopback); + listener.Listen(1); + + await client.ConnectAsync(listener.LocalEndPoint); + using (Socket server = await listener.AcceptAsync()) + { + CancellationTokenSource cts; + + for (int len = 0; len < 2; len++) + { + cts = new CancellationTokenSource(); + ValueTask vt = server.ReceiveAsync((Memory)new byte[len], SocketFlags.None, cts.Token); + Assert.False(vt.IsCompleted); + cts.Cancel(); + await Assert.ThrowsAnyAsync(async () => await vt); + } + + // Make sure subsequent operations aren't canceled. + await server.SendAsync((ReadOnlyMemory)new byte[1], SocketFlags.None); + Assert.Equal(1, await client.ReceiveAsync((Memory)new byte[10], SocketFlags.None)); + } + } + } + + [Fact] + public async Task CanceledOneOfMultipleReceives_Udp_Throws() + { + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + client.Bind(new IPEndPoint(IPAddress.Loopback, 0)); + + var cts = new CancellationTokenSource(); + + // Create three UDP receives, only one of which we'll cancel. + byte[] buffer1 = new byte[1], buffer2 = new byte[1], buffer3 = new byte[1]; + ValueTask r1 = client.ReceiveAsync(buffer1.AsMemory(), SocketFlags.None, cts.Token); + ValueTask r2 = client.ReceiveAsync(buffer2.AsMemory(), SocketFlags.None); + ValueTask r3 = client.ReceiveAsync(buffer3.AsMemory(), SocketFlags.None); + + // Cancel one of them, and validate it's been canceled. + cts.Cancel(); + await Assert.ThrowsAnyAsync(async () => await r1); + Assert.Equal(0, buffer1[0]); + + // Send data to complete the others, and validate they complete successfully. + using (var server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) + { + server.SendTo(new byte[1] { 42 }, client.LocalEndPoint); + server.SendTo(new byte[1] { 43 }, client.LocalEndPoint); + } + + Assert.Equal(1, await r2); + Assert.Equal(1, await r3); + Assert.True( + (buffer2[0] == 42 && buffer3[0] == 43) || + (buffer2[0] == 43 && buffer3[0] == 42), + $"buffer2[0]={buffer2[0]}, buffer3[0]={buffer3[0]}"); + } + } + [Fact] public async Task DisposedSocket_ThrowsOperationCanceledException() { diff --git a/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs b/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs index 075fcd60a08a..daaa2c1e14b0 100644 --- a/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs +++ b/src/System.Net.Sockets/tests/FunctionalTests/SocketOptionNameTest.cs @@ -17,7 +17,6 @@ public partial class SocketOptionNameTest { private static bool SocketsReuseUnicastPortSupport => Capability.SocketsReuseUnicastPortSupport().HasValue; - [OuterLoop] // TODO: Issue #11345 [ConditionalFact(nameof(SocketsReuseUnicastPortSupport))] public void ReuseUnicastPort_CreateSocketGetOption() { @@ -34,7 +33,6 @@ public void ReuseUnicastPort_CreateSocketGetOption() } } - [OuterLoop] // TODO: Issue #11345 [ConditionalFact(nameof(SocketsReuseUnicastPortSupport))] public void ReuseUnicastPort_CreateSocketSetOption() { @@ -53,22 +51,6 @@ public void ReuseUnicastPort_CreateSocketSetOption() } } - // TODO: Issue #4887 - // The socket option 'ReuseUnicastPost' only works on Windows 10 systems. In addition, setting the option - // is a no-op unless specialized network settings using PowerShell configuration are first applied to the - // machine. This is currently difficult to test in the CI environment. So, this test will be disabled for now - [OuterLoop] // TODO: Issue #11345 - [ActiveIssue(4887)] - public void ReuseUnicastPort_CreateSocketSetOptionToOneAndGetOption_SocketsReuseUnicastPortSupport_OptionIsOne() - { - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - - socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort, 1); - int optionValue = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseUnicastPort); - Assert.Equal(1, optionValue); - } - - [OuterLoop] // TODO: Issue #11345 [Fact] public void MulticastOption_CreateSocketSetGetOption_GroupAndInterfaceIndex_SetSucceeds_GetThrows() { @@ -83,18 +65,16 @@ public void MulticastOption_CreateSocketSetGetOption_GroupAndInterfaceIndex_SetS } } - [OuterLoop] // TODO: Issue #11345 - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // ActiveIssue: dotnet/corefx #29929 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // Skip on Nano: dotnet/corefx #29929 public async Task MulticastInterface_Set_AnyInterface_Succeeds() { // On all platforms, index 0 means "any interface" await MulticastInterface_Set_Helper(0); } - [OuterLoop] // TODO: Issue #11345 - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // ActiveIssue: dotnet/corefx #29929 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // Skip on Nano: dotnet/corefx #29929 [PlatformSpecific(TestPlatforms.Windows)] // see comment below - [ActiveIssue(21327, TargetFrameworkMonikers.Uap)] // UWP Apps are forbidden to send network traffic to the local Computer. + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // UWP Apps are normally blocked to send network traffic on loopback. public async Task MulticastInterface_Set_Loopback_Succeeds() { // On Windows, we can apparently assume interface 1 is "loopback." On other platforms, this is not a @@ -138,7 +118,6 @@ private async Task MulticastInterface_Set_Helper(int interfaceIndex) } } - [OuterLoop] // TODO: Issue #11345 [Fact] public void MulticastInterface_Set_InvalidIndex_Throws() { @@ -150,8 +129,7 @@ public void MulticastInterface_Set_InvalidIndex_Throws() } } - [OuterLoop] // TODO: Issue #11345 - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // ActiveIssue: dotnet/corefx #29929 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // Skip on Nano: dotnet/corefx #29929 [PlatformSpecific(~TestPlatforms.OSX)] public async Task MulticastInterface_Set_IPv6_AnyInterface_Succeeds() { @@ -179,7 +157,7 @@ public void MulticastTTL_Set_IPv4_Succeeds() } } - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // ActiveIssue: dotnet/corefx #29929 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // Skip on Nano: dotnet/corefx #29929 public void MulticastTTL_Set_IPv6_Succeeds() { using (Socket socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp)) @@ -208,10 +186,9 @@ public void Ttl_Set_Succeeds(AddressFamily af) } } - [OuterLoop] // TODO: Issue #11345 - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // ActiveIssue: dotnet/corefx #29929 + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] // Skip on Nano: dotnet/corefx #29929 [PlatformSpecific(TestPlatforms.Windows)] - [ActiveIssue(21327, TargetFrameworkMonikers.Uap)] // UWP Apps are forbidden to send network traffic to the local Computer. + [SkipOnTargetFramework(TargetFrameworkMonikers.Uap)] // UWP Apps are normally blocked to send network traffic on loopback. public async void MulticastInterface_Set_IPv6_Loopback_Succeeds() { // On Windows, we can apparently assume interface 1 is "loopback." On other platforms, this is not a @@ -266,7 +243,6 @@ public void MulticastInterface_Set_IPv6_InvalidIndex_Throws() } } - [OuterLoop] // TODO: Issue #11345 [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // In WSL, the connect() call fails immediately. [InlineData(false)] [InlineData(true)] @@ -524,7 +500,6 @@ public unsafe void ReuseAddressUdp() } } - [OuterLoop] // TODO: Issue #11345 [Theory] [PlatformSpecific(TestPlatforms.Windows)] // SetIPProtectionLevel not supported on Unix [InlineData(IPProtectionLevel.EdgeRestricted, AddressFamily.InterNetwork, SocketOptionLevel.IP)] @@ -544,7 +519,6 @@ public void SetIPProtectionLevel_Windows(IPProtectionLevel level, AddressFamily } } - [OuterLoop] // TODO: Issue #11345 [Theory] [PlatformSpecific(TestPlatforms.AnyUnix)] // SetIPProtectionLevel not supported on Unix [InlineData(IPProtectionLevel.EdgeRestricted, AddressFamily.InterNetwork)] @@ -561,7 +535,6 @@ public void SetIPProtectionLevel_Unix(IPProtectionLevel level, AddressFamily fam } } - [OuterLoop] // TODO: Issue #11345 [Theory] [InlineData(AddressFamily.InterNetwork)] [InlineData(AddressFamily.InterNetworkV6)] diff --git a/src/System.Net.WebClient/tests/WebClientTest.cs b/src/System.Net.WebClient/tests/WebClientTest.cs index dd36f3acbcd0..52031c9883ef 100644 --- a/src/System.Net.WebClient/tests/WebClientTest.cs +++ b/src/System.Net.WebClient/tests/WebClientTest.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Net.Cache; using System.Net.Test.Common; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -478,6 +479,8 @@ public abstract class WebClientTestBase public const int TimeoutMilliseconds = 30 * 1000; public static readonly object[][] EchoServers = Configuration.Http.EchoServers; + public static readonly object[][] VerifyUploadServers = Configuration.Http.VerifyUploadServers; + const string ExpectedText = "To be, or not to be, that is the question:" + "Whether 'tis Nobler in the mind to suffer" + @@ -628,16 +631,17 @@ public async Task OpenWrite_Success(Uri echoServer) [OuterLoop("Uses external servers")] [Theory] - [MemberData(nameof(EchoServers))] - public async Task UploadData_Success(Uri echoServer) + [MemberData(nameof(VerifyUploadServers))] + public async Task UploadData_Success(Uri server) { var wc = new WebClient(); var uploadProgressInvoked = new TaskCompletionSource(); wc.UploadProgressChanged += (s, e) => uploadProgressInvoked.TrySetResult(true); // to enable chunking of the upload - byte[] result = await UploadDataAsync(wc, echoServer.ToString(), Encoding.UTF8.GetBytes(ExpectedText)); - Assert.Contains(ExpectedText, Encoding.UTF8.GetString(result)); + // Server will verify uploaded data. An exception will be thrown if there is a problem. + AddMD5Header(wc, ExpectedText); + byte[] ignored = await UploadDataAsync(wc, server.ToString(), Encoding.UTF8.GetBytes(ExpectedText)); if(IsAsync) { await uploadProgressInvoked.Task.TimeoutAfter(TimeoutMilliseconds); @@ -646,13 +650,15 @@ public async Task UploadData_Success(Uri echoServer) [OuterLoop("Uses external servers")] [Theory] - [MemberData(nameof(EchoServers))] - public async Task UploadData_LargeData_Success(Uri echoServer) + [MemberData(nameof(VerifyUploadServers))] + public async Task UploadData_LargeData_Success(Uri server) { var wc = new WebClient(); string largeText = GetRandomText(512 * 1024); - byte[] result = await UploadDataAsync(wc, echoServer.ToString(), Encoding.UTF8.GetBytes(largeText)); - Assert.Contains(largeText, Encoding.UTF8.GetString(result)); + + // Server will verify uploaded data. An exception will be thrown if there is a problem. + AddMD5Header(wc, largeText); + byte[] ignored = await UploadDataAsync(wc, server.ToString(), Encoding.UTF8.GetBytes(largeText)); } private static string GetRandomText(int length) @@ -682,12 +688,14 @@ public async Task UploadFile_Success(Uri echoServer) [OuterLoop("Uses external servers")] [Theory] - [MemberData(nameof(EchoServers))] - public async Task UploadString_Success(Uri echoServer) + [MemberData(nameof(VerifyUploadServers))] + public async Task UploadString_Success(Uri server) { var wc = new WebClient(); - string result = await UploadStringAsync(wc, echoServer.ToString(), ExpectedText); - Assert.Contains(ExpectedText, result); + + // Server will verify uploaded data. An exception will be thrown if there is a problem. + AddMD5Header(wc, ExpectedText); + string ignored = await UploadStringAsync(wc, server.ToString(), ExpectedText); } [OuterLoop("Uses external servers")] @@ -699,6 +707,17 @@ public async Task UploadValues_Success(Uri echoServer) byte[] result = await UploadValuesAsync(wc, echoServer.ToString(), new NameValueCollection() { { "Data", ExpectedText } }); Assert.Contains(ExpectedTextAfterUrlEncode, Encoding.UTF8.GetString(result)); } + + private static void AddMD5Header(WebClient wc, string data) + { + using (MD5 md5 = MD5.Create()) + { + // Compute MD5 hash of the data that will be uploaded. We convert the string to UTF-8 since + // that is the encoding used by WebClient when serializing the data on the wire. + string headerValue = Convert.ToBase64String(md5.ComputeHash(Encoding.UTF8.GetBytes(data))); + wc.Headers.Add("Content-MD5", headerValue); + } + } } public class SyncWebClientTest : WebClientTestBase diff --git a/src/System.Net.WebSockets.Client/tests/ConnectTest.cs b/src/System.Net.WebSockets.Client/tests/ConnectTest.cs index 2d83ecff250c..37d7e4655e51 100644 --- a/src/System.Net.WebSockets.Client/tests/ConnectTest.cs +++ b/src/System.Net.WebSockets.Client/tests/ConnectTest.cs @@ -16,7 +16,7 @@ public class ConnectTest : ClientWebSocketTestBase { public ConnectTest(ITestOutputHelper output) : base(output) { } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(UnavailableWebSocketServers))] public async Task ConnectAsync_NotWebSocketServer_ThrowsWebSocketExceptionWithMessage(Uri server, string exceptionMessage, WebSocketError errorCode) { @@ -40,21 +40,21 @@ public async Task ConnectAsync_NotWebSocketServer_ThrowsWebSocketExceptionWithMe } } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task EchoBinaryMessage_Success(Uri server) { await WebSocketHelper.TestEcho(server, WebSocketMessageType.Binary, TimeOutMilliseconds, _output); } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task EchoTextMessage_Success(Uri server) { await WebSocketHelper.TestEcho(server, WebSocketMessageType.Text, TimeOutMilliseconds, _output); } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoHeadersServers))] public async Task ConnectAsync_AddCustomHeaders_Success(Uri server) { @@ -92,7 +92,7 @@ public async Task ConnectAsync_AddCustomHeaders_Success(Uri server) } [ActiveIssue(18784, TargetFrameworkMonikers.NetFramework)] - [OuterLoop] + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported))] public async Task ConnectAsync_AddHostHeader_Success() { @@ -136,7 +136,7 @@ public async Task ConnectAsync_AddHostHeader_Success() } } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoHeadersServers))] public async Task ConnectAsync_CookieHeaders_Success(Uri server) { @@ -185,7 +185,7 @@ public async Task ConnectAsync_CookieHeaders_Success(Uri server) } } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ConnectAsync_PassNoSubProtocol_ServerRequires_ThrowsWebSocketException(Uri server) { @@ -210,7 +210,7 @@ public async Task ConnectAsync_PassNoSubProtocol_ServerRequires_ThrowsWebSocketE } } - [OuterLoop] // TODO: Issue #11345 + [OuterLoop("Uses external servers")] [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] public async Task ConnectAsync_PassMultipleSubProtocols_ServerRequires_ConnectionUsesAgreedSubProtocol(Uri server) { @@ -248,5 +248,25 @@ await LoopbackServer.CreateClientAndServerAsync(async uri => Assert.True(await LoopbackHelper.WebSocketHandshakeAsync(connection)); }), new LoopbackServer.Options { WebSocketEndpoint = true }); } + + [OuterLoop("Uses external servers")] + [ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))] + public async Task ConnectAndCloseAsync_UseProxyServer_ExpectedClosedState(Uri server) + { + using (var cws = new ClientWebSocket()) + using (var cts = new CancellationTokenSource(TimeOutMilliseconds)) + using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create()) + { + cws.Options.Proxy = new WebProxy(proxyServer.Uri); + await cws.ConnectAsync(server, cts.Token); + + string expectedCloseStatusDescription = "Client close status"; + await cws.CloseAsync(WebSocketCloseStatus.NormalClosure, expectedCloseStatusDescription, cts.Token); + + Assert.Equal(WebSocketState.Closed, cws.State); + Assert.Equal(WebSocketCloseStatus.NormalClosure, cws.CloseStatus); + Assert.Equal(expectedCloseStatusDescription, cws.CloseStatusDescription); + } + } } } diff --git a/src/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj b/src/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj index 8849fdb1e865..65f1f0f0149d 100644 --- a/src/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj +++ b/src/System.Net.WebSockets.Client/tests/System.Net.WebSockets.Client.Tests.csproj @@ -28,6 +28,9 @@ Common\System\Net\Configuration.WebSockets.cs + + Common\System\Net\Http\LoopbackProxyServer.cs + Common\System\Net\Http\LoopbackServer.cs @@ -58,6 +61,6 @@ - + diff --git a/src/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs b/src/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs index 5765d185439e..1202bab3e86e 100644 --- a/src/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs +++ b/src/System.Numerics.Vectors/ref/System.Numerics.Vectors.cs @@ -17,8 +17,8 @@ public partial struct Matrix3x2 : System.IEquatable public float M32; public Matrix3x2(float m11, float m12, float m21, float m22, float m31, float m32) { throw null; } public static System.Numerics.Matrix3x2 Identity { get { throw null; } } - public bool IsIdentity { get { throw null; } } - public System.Numerics.Vector2 Translation { get { throw null; } set { } } + public readonly bool IsIdentity { get { throw null; } } + public System.Numerics.Vector2 Translation { readonly get { throw null; } set { } } public static System.Numerics.Matrix3x2 Add(System.Numerics.Matrix3x2 value1, System.Numerics.Matrix3x2 value2) { throw null; } public static System.Numerics.Matrix3x2 CreateRotation(float radians) { throw null; } public static System.Numerics.Matrix3x2 CreateRotation(float radians, System.Numerics.Vector2 centerPoint) { throw null; } @@ -32,10 +32,10 @@ public partial struct Matrix3x2 : System.IEquatable public static System.Numerics.Matrix3x2 CreateSkew(float radiansX, float radiansY, System.Numerics.Vector2 centerPoint) { throw null; } public static System.Numerics.Matrix3x2 CreateTranslation(System.Numerics.Vector2 position) { throw null; } public static System.Numerics.Matrix3x2 CreateTranslation(float xPosition, float yPosition) { throw null; } - public bool Equals(System.Numerics.Matrix3x2 other) { throw null; } - public override bool Equals(object obj) { throw null; } - public float GetDeterminant() { throw null; } - public override int GetHashCode() { throw null; } + public readonly bool Equals(System.Numerics.Matrix3x2 other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public readonly float GetDeterminant() { throw null; } + public override readonly int GetHashCode() { throw null; } public static bool Invert(System.Numerics.Matrix3x2 matrix, out System.Numerics.Matrix3x2 result) { throw null; } public static System.Numerics.Matrix3x2 Lerp(System.Numerics.Matrix3x2 matrix1, System.Numerics.Matrix3x2 matrix2, float amount) { throw null; } public static System.Numerics.Matrix3x2 Multiply(System.Numerics.Matrix3x2 value1, System.Numerics.Matrix3x2 value2) { throw null; } @@ -49,7 +49,7 @@ public partial struct Matrix3x2 : System.IEquatable public static System.Numerics.Matrix3x2 operator -(System.Numerics.Matrix3x2 value1, System.Numerics.Matrix3x2 value2) { throw null; } public static System.Numerics.Matrix3x2 operator -(System.Numerics.Matrix3x2 value) { throw null; } public static System.Numerics.Matrix3x2 Subtract(System.Numerics.Matrix3x2 value1, System.Numerics.Matrix3x2 value2) { throw null; } - public override string ToString() { throw null; } + public override readonly string ToString() { throw null; } } public partial struct Matrix4x4 : System.IEquatable { @@ -72,8 +72,8 @@ public partial struct Matrix4x4 : System.IEquatable public Matrix4x4(System.Numerics.Matrix3x2 value) { throw null; } public Matrix4x4(float m11, float m12, float m13, float m14, float m21, float m22, float m23, float m24, float m31, float m32, float m33, float m34, float m41, float m42, float m43, float m44) { throw null; } public static System.Numerics.Matrix4x4 Identity { get { throw null; } } - public bool IsIdentity { get { throw null; } } - public System.Numerics.Vector3 Translation { get { throw null; } set { } } + public readonly bool IsIdentity { get { throw null; } } + public System.Numerics.Vector3 Translation { readonly get { throw null; } set { } } public static System.Numerics.Matrix4x4 Add(System.Numerics.Matrix4x4 value1, System.Numerics.Matrix4x4 value2) { throw null; } public static System.Numerics.Matrix4x4 CreateBillboard(System.Numerics.Vector3 objectPosition, System.Numerics.Vector3 cameraPosition, System.Numerics.Vector3 cameraUpVector, System.Numerics.Vector3 cameraForwardVector) { throw null; } public static System.Numerics.Matrix4x4 CreateConstrainedBillboard(System.Numerics.Vector3 objectPosition, System.Numerics.Vector3 cameraPosition, System.Numerics.Vector3 rotateAxis, System.Numerics.Vector3 cameraForwardVector, System.Numerics.Vector3 objectForwardVector) { throw null; } @@ -104,10 +104,10 @@ public partial struct Matrix4x4 : System.IEquatable public static System.Numerics.Matrix4x4 CreateTranslation(float xPosition, float yPosition, float zPosition) { throw null; } public static System.Numerics.Matrix4x4 CreateWorld(System.Numerics.Vector3 position, System.Numerics.Vector3 forward, System.Numerics.Vector3 up) { throw null; } public static bool Decompose(System.Numerics.Matrix4x4 matrix, out System.Numerics.Vector3 scale, out System.Numerics.Quaternion rotation, out System.Numerics.Vector3 translation) { throw null; } - public bool Equals(System.Numerics.Matrix4x4 other) { throw null; } - public override bool Equals(object obj) { throw null; } - public float GetDeterminant() { throw null; } - public override int GetHashCode() { throw null; } + public readonly bool Equals(System.Numerics.Matrix4x4 other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public readonly float GetDeterminant() { throw null; } + public override readonly int GetHashCode() { throw null; } public static bool Invert(System.Numerics.Matrix4x4 matrix, out System.Numerics.Matrix4x4 result) { throw null; } public static System.Numerics.Matrix4x4 Lerp(System.Numerics.Matrix4x4 matrix1, System.Numerics.Matrix4x4 matrix2, float amount) { throw null; } public static System.Numerics.Matrix4x4 Multiply(System.Numerics.Matrix4x4 value1, System.Numerics.Matrix4x4 value2) { throw null; } @@ -121,7 +121,7 @@ public partial struct Matrix4x4 : System.IEquatable public static System.Numerics.Matrix4x4 operator -(System.Numerics.Matrix4x4 value1, System.Numerics.Matrix4x4 value2) { throw null; } public static System.Numerics.Matrix4x4 operator -(System.Numerics.Matrix4x4 value) { throw null; } public static System.Numerics.Matrix4x4 Subtract(System.Numerics.Matrix4x4 value1, System.Numerics.Matrix4x4 value2) { throw null; } - public override string ToString() { throw null; } + public override readonly string ToString() { throw null; } public static System.Numerics.Matrix4x4 Transform(System.Numerics.Matrix4x4 value, System.Numerics.Quaternion rotation) { throw null; } public static System.Numerics.Matrix4x4 Transpose(System.Numerics.Matrix4x4 matrix) { throw null; } } @@ -136,13 +136,13 @@ public partial struct Plane : System.IEquatable public static float Dot(System.Numerics.Plane plane, System.Numerics.Vector4 value) { throw null; } public static float DotCoordinate(System.Numerics.Plane plane, System.Numerics.Vector3 value) { throw null; } public static float DotNormal(System.Numerics.Plane plane, System.Numerics.Vector3 value) { throw null; } - public bool Equals(System.Numerics.Plane other) { throw null; } - public override bool Equals(object obj) { throw null; } - public override int GetHashCode() { throw null; } + public readonly bool Equals(System.Numerics.Plane other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public override readonly int GetHashCode() { throw null; } public static System.Numerics.Plane Normalize(System.Numerics.Plane value) { throw null; } public static bool operator ==(System.Numerics.Plane value1, System.Numerics.Plane value2) { throw null; } public static bool operator !=(System.Numerics.Plane value1, System.Numerics.Plane value2) { throw null; } - public override string ToString() { throw null; } + public override readonly string ToString() { throw null; } public static System.Numerics.Plane Transform(System.Numerics.Plane plane, System.Numerics.Matrix4x4 matrix) { throw null; } public static System.Numerics.Plane Transform(System.Numerics.Plane plane, System.Numerics.Quaternion rotation) { throw null; } } @@ -155,7 +155,7 @@ public partial struct Quaternion : System.IEquatable public Quaternion(System.Numerics.Vector3 vectorPart, float scalarPart) { throw null; } public Quaternion(float x, float y, float z, float w) { throw null; } public static System.Numerics.Quaternion Identity { get { throw null; } } - public bool IsIdentity { get { throw null; } } + public readonly bool IsIdentity { get { throw null; } } public static System.Numerics.Quaternion Add(System.Numerics.Quaternion value1, System.Numerics.Quaternion value2) { throw null; } public static System.Numerics.Quaternion Concatenate(System.Numerics.Quaternion value1, System.Numerics.Quaternion value2) { throw null; } public static System.Numerics.Quaternion Conjugate(System.Numerics.Quaternion value) { throw null; } @@ -164,12 +164,12 @@ public partial struct Quaternion : System.IEquatable public static System.Numerics.Quaternion CreateFromYawPitchRoll(float yaw, float pitch, float roll) { throw null; } public static System.Numerics.Quaternion Divide(System.Numerics.Quaternion value1, System.Numerics.Quaternion value2) { throw null; } public static float Dot(System.Numerics.Quaternion quaternion1, System.Numerics.Quaternion quaternion2) { throw null; } - public bool Equals(System.Numerics.Quaternion other) { throw null; } - public override bool Equals(object obj) { throw null; } - public override int GetHashCode() { throw null; } + public readonly bool Equals(System.Numerics.Quaternion other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public override readonly int GetHashCode() { throw null; } public static System.Numerics.Quaternion Inverse(System.Numerics.Quaternion value) { throw null; } - public float Length() { throw null; } - public float LengthSquared() { throw null; } + public readonly float Length() { throw null; } + public readonly float LengthSquared() { throw null; } public static System.Numerics.Quaternion Lerp(System.Numerics.Quaternion quaternion1, System.Numerics.Quaternion quaternion2, float amount) { throw null; } public static System.Numerics.Quaternion Multiply(System.Numerics.Quaternion value1, System.Numerics.Quaternion value2) { throw null; } public static System.Numerics.Quaternion Multiply(System.Numerics.Quaternion value1, float value2) { throw null; } @@ -185,7 +185,7 @@ public partial struct Quaternion : System.IEquatable public static System.Numerics.Quaternion operator -(System.Numerics.Quaternion value) { throw null; } public static System.Numerics.Quaternion Slerp(System.Numerics.Quaternion quaternion1, System.Numerics.Quaternion quaternion2, float amount) { throw null; } public static System.Numerics.Quaternion Subtract(System.Numerics.Quaternion value1, System.Numerics.Quaternion value2) { throw null; } - public override string ToString() { throw null; } + public override readonly string ToString() { throw null; } } public static partial class Vector { @@ -307,18 +307,18 @@ public partial struct Vector2 : System.IEquatable, Syst public static System.Numerics.Vector2 Abs(System.Numerics.Vector2 value) { throw null; } public static System.Numerics.Vector2 Add(System.Numerics.Vector2 left, System.Numerics.Vector2 right) { throw null; } public static System.Numerics.Vector2 Clamp(System.Numerics.Vector2 value1, System.Numerics.Vector2 min, System.Numerics.Vector2 max) { throw null; } - public void CopyTo(float[] array) { } - public void CopyTo(float[] array, int index) { } + public readonly void CopyTo(float[] array) { } + public readonly void CopyTo(float[] array, int index) { } public static float Distance(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2) { throw null; } public static float DistanceSquared(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2) { throw null; } public static System.Numerics.Vector2 Divide(System.Numerics.Vector2 left, System.Numerics.Vector2 right) { throw null; } public static System.Numerics.Vector2 Divide(System.Numerics.Vector2 left, float divisor) { throw null; } public static float Dot(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2) { throw null; } - public bool Equals(System.Numerics.Vector2 other) { throw null; } - public override bool Equals(object obj) { throw null; } - public override int GetHashCode() { throw null; } - public float Length() { throw null; } - public float LengthSquared() { throw null; } + public readonly bool Equals(System.Numerics.Vector2 other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly float Length() { throw null; } + public readonly float LengthSquared() { throw null; } public static System.Numerics.Vector2 Lerp(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2, float amount) { throw null; } public static System.Numerics.Vector2 Max(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2) { throw null; } public static System.Numerics.Vector2 Min(System.Numerics.Vector2 value1, System.Numerics.Vector2 value2) { throw null; } @@ -340,9 +340,9 @@ public void CopyTo(float[] array, int index) { } public static System.Numerics.Vector2 Reflect(System.Numerics.Vector2 vector, System.Numerics.Vector2 normal) { throw null; } public static System.Numerics.Vector2 SquareRoot(System.Numerics.Vector2 value) { throw null; } public static System.Numerics.Vector2 Subtract(System.Numerics.Vector2 left, System.Numerics.Vector2 right) { throw null; } - public override string ToString() { throw null; } - public string ToString(string format) { throw null; } - public string ToString(string format, System.IFormatProvider formatProvider) { throw null; } + public override readonly string ToString() { throw null; } + public readonly string ToString(string format) { throw null; } + public readonly string ToString(string format, System.IFormatProvider formatProvider) { throw null; } public static System.Numerics.Vector2 Transform(System.Numerics.Vector2 position, System.Numerics.Matrix3x2 matrix) { throw null; } public static System.Numerics.Vector2 Transform(System.Numerics.Vector2 position, System.Numerics.Matrix4x4 matrix) { throw null; } public static System.Numerics.Vector2 Transform(System.Numerics.Vector2 value, System.Numerics.Quaternion rotation) { throw null; } @@ -365,19 +365,19 @@ public partial struct Vector3 : System.IEquatable, Syst public static System.Numerics.Vector3 Abs(System.Numerics.Vector3 value) { throw null; } public static System.Numerics.Vector3 Add(System.Numerics.Vector3 left, System.Numerics.Vector3 right) { throw null; } public static System.Numerics.Vector3 Clamp(System.Numerics.Vector3 value1, System.Numerics.Vector3 min, System.Numerics.Vector3 max) { throw null; } - public void CopyTo(float[] array) { } - public void CopyTo(float[] array, int index) { } + public readonly void CopyTo(float[] array) { } + public readonly void CopyTo(float[] array, int index) { } public static System.Numerics.Vector3 Cross(System.Numerics.Vector3 vector1, System.Numerics.Vector3 vector2) { throw null; } public static float Distance(System.Numerics.Vector3 value1, System.Numerics.Vector3 value2) { throw null; } public static float DistanceSquared(System.Numerics.Vector3 value1, System.Numerics.Vector3 value2) { throw null; } public static System.Numerics.Vector3 Divide(System.Numerics.Vector3 left, System.Numerics.Vector3 right) { throw null; } public static System.Numerics.Vector3 Divide(System.Numerics.Vector3 left, float divisor) { throw null; } public static float Dot(System.Numerics.Vector3 vector1, System.Numerics.Vector3 vector2) { throw null; } - public bool Equals(System.Numerics.Vector3 other) { throw null; } - public override bool Equals(object obj) { throw null; } - public override int GetHashCode() { throw null; } - public float Length() { throw null; } - public float LengthSquared() { throw null; } + public readonly bool Equals(System.Numerics.Vector3 other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly float Length() { throw null; } + public readonly float LengthSquared() { throw null; } public static System.Numerics.Vector3 Lerp(System.Numerics.Vector3 value1, System.Numerics.Vector3 value2, float amount) { throw null; } public static System.Numerics.Vector3 Max(System.Numerics.Vector3 value1, System.Numerics.Vector3 value2) { throw null; } public static System.Numerics.Vector3 Min(System.Numerics.Vector3 value1, System.Numerics.Vector3 value2) { throw null; } @@ -399,9 +399,9 @@ public void CopyTo(float[] array, int index) { } public static System.Numerics.Vector3 Reflect(System.Numerics.Vector3 vector, System.Numerics.Vector3 normal) { throw null; } public static System.Numerics.Vector3 SquareRoot(System.Numerics.Vector3 value) { throw null; } public static System.Numerics.Vector3 Subtract(System.Numerics.Vector3 left, System.Numerics.Vector3 right) { throw null; } - public override string ToString() { throw null; } - public string ToString(string format) { throw null; } - public string ToString(string format, System.IFormatProvider formatProvider) { throw null; } + public override readonly string ToString() { throw null; } + public readonly string ToString(string format) { throw null; } + public readonly string ToString(string format, System.IFormatProvider formatProvider) { throw null; } public static System.Numerics.Vector3 Transform(System.Numerics.Vector3 position, System.Numerics.Matrix4x4 matrix) { throw null; } public static System.Numerics.Vector3 Transform(System.Numerics.Vector3 value, System.Numerics.Quaternion rotation) { throw null; } public static System.Numerics.Vector3 TransformNormal(System.Numerics.Vector3 normal, System.Numerics.Matrix4x4 matrix) { throw null; } @@ -425,18 +425,18 @@ public partial struct Vector4 : System.IEquatable, Syst public static System.Numerics.Vector4 Abs(System.Numerics.Vector4 value) { throw null; } public static System.Numerics.Vector4 Add(System.Numerics.Vector4 left, System.Numerics.Vector4 right) { throw null; } public static System.Numerics.Vector4 Clamp(System.Numerics.Vector4 value1, System.Numerics.Vector4 min, System.Numerics.Vector4 max) { throw null; } - public void CopyTo(float[] array) { } - public void CopyTo(float[] array, int index) { } + public readonly void CopyTo(float[] array) { } + public readonly void CopyTo(float[] array, int index) { } public static float Distance(System.Numerics.Vector4 value1, System.Numerics.Vector4 value2) { throw null; } public static float DistanceSquared(System.Numerics.Vector4 value1, System.Numerics.Vector4 value2) { throw null; } public static System.Numerics.Vector4 Divide(System.Numerics.Vector4 left, System.Numerics.Vector4 right) { throw null; } public static System.Numerics.Vector4 Divide(System.Numerics.Vector4 left, float divisor) { throw null; } public static float Dot(System.Numerics.Vector4 vector1, System.Numerics.Vector4 vector2) { throw null; } - public bool Equals(System.Numerics.Vector4 other) { throw null; } - public override bool Equals(object obj) { throw null; } - public override int GetHashCode() { throw null; } - public float Length() { throw null; } - public float LengthSquared() { throw null; } + public readonly bool Equals(System.Numerics.Vector4 other) { throw null; } + public override readonly bool Equals(object obj) { throw null; } + public override readonly int GetHashCode() { throw null; } + public readonly float Length() { throw null; } + public readonly float LengthSquared() { throw null; } public static System.Numerics.Vector4 Lerp(System.Numerics.Vector4 value1, System.Numerics.Vector4 value2, float amount) { throw null; } public static System.Numerics.Vector4 Max(System.Numerics.Vector4 value1, System.Numerics.Vector4 value2) { throw null; } public static System.Numerics.Vector4 Min(System.Numerics.Vector4 value1, System.Numerics.Vector4 value2) { throw null; } @@ -457,9 +457,9 @@ public void CopyTo(float[] array, int index) { } public static System.Numerics.Vector4 operator -(System.Numerics.Vector4 value) { throw null; } public static System.Numerics.Vector4 SquareRoot(System.Numerics.Vector4 value) { throw null; } public static System.Numerics.Vector4 Subtract(System.Numerics.Vector4 left, System.Numerics.Vector4 right) { throw null; } - public override string ToString() { throw null; } - public string ToString(string format) { throw null; } - public string ToString(string format, System.IFormatProvider formatProvider) { throw null; } + public override readonly string ToString() { throw null; } + public readonly string ToString(string format) { throw null; } + public readonly string ToString(string format, System.IFormatProvider formatProvider) { throw null; } public static System.Numerics.Vector4 Transform(System.Numerics.Vector2 position, System.Numerics.Matrix4x4 matrix) { throw null; } public static System.Numerics.Vector4 Transform(System.Numerics.Vector2 value, System.Numerics.Quaternion rotation) { throw null; } public static System.Numerics.Vector4 Transform(System.Numerics.Vector3 position, System.Numerics.Matrix4x4 matrix) { throw null; } diff --git a/src/System.Numerics.Vectors/src/Configurations.props b/src/System.Numerics.Vectors/src/Configurations.props index d795f8886d3b..6a0c1d593ec7 100644 --- a/src/System.Numerics.Vectors/src/Configurations.props +++ b/src/System.Numerics.Vectors/src/Configurations.props @@ -10,8 +10,6 @@ uap-Windows_NT; netfx-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; $(PackageConfigurations) diff --git a/src/System.Numerics.Vectors/src/System.Numerics.Vectors.csproj b/src/System.Numerics.Vectors/src/System.Numerics.Vectors.csproj index 58014e82923d..b35c637c1985 100644 --- a/src/System.Numerics.Vectors/src/System.Numerics.Vectors.csproj +++ b/src/System.Numerics.Vectors/src/System.Numerics.Vectors.csproj @@ -8,7 +8,7 @@ true $(DefineConstants);HAS_INTRINSICS netstandard1.0;portable-net45+win8+wp8+wpa81 - net46-Debug;net46-Release;netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard-Debug;netstandard-Release;netstandard1.0-Debug;netstandard1.0-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + net46-Debug;net46-Release;netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard-Debug;netstandard-Release;netstandard1.0-Debug;netstandard1.0-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Matrix3x2.cs b/src/System.Numerics.Vectors/src/System/Numerics/Matrix3x2.cs index effd34446d16..8960138b5bef 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Matrix3x2.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Matrix3x2.cs @@ -58,7 +58,7 @@ public static Matrix3x2 Identity /// /// Returns whether the matrix is the identity matrix. /// - public bool IsIdentity + public readonly bool IsIdentity { get { @@ -74,7 +74,7 @@ public bool IsIdentity /// public Vector2 Translation { - get + readonly get { return new Vector2(M31, M32); } @@ -443,7 +443,7 @@ public static Matrix3x2 CreateRotation(float radians, Vector2 centerPoint) /// The determinant is calculated by expanding the matrix with a third column whose values are (0,0,1). /// /// The determinant. - public float GetDeterminant() + public readonly float GetDeterminant() { // There isn't actually any such thing as a determinant for a non-square matrix, // but this 3x2 type is really just an optimization of a 3x3 where we happen to @@ -758,7 +758,7 @@ public static Matrix3x2 Multiply(Matrix3x2 value1, float value2) /// /// The other matrix to test equality against. /// True if this matrix is equal to other; False otherwise. - public bool Equals(Matrix3x2 other) + public readonly bool Equals(Matrix3x2 other) { return (M11 == other.M11 && M22 == other.M22 && // Check diagonal element first for early out. M12 == other.M12 && @@ -771,7 +771,7 @@ public bool Equals(Matrix3x2 other) /// /// The Object to compare against. /// True if the Object is equal to this matrix; False otherwise. - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Matrix3x2) { @@ -785,7 +785,7 @@ public override bool Equals(object obj) /// Returns a String representing this matrix instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return string.Format(CultureInfo.CurrentCulture, "{{ {{M11:{0} M12:{1}}} {{M21:{2} M22:{3}}} {{M31:{4} M32:{5}}} }}", M11, M12, @@ -797,7 +797,7 @@ public override string ToString() /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { return unchecked(M11.GetHashCode() + M12.GetHashCode() + M21.GetHashCode() + M22.GetHashCode() + diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Matrix4x4.cs b/src/System.Numerics.Vectors/src/System/Numerics/Matrix4x4.cs index 347933952cb5..aedbc4a693ad 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Matrix4x4.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Matrix4x4.cs @@ -110,7 +110,7 @@ public static Matrix4x4 Identity /// /// Returns whether the matrix is the identity matrix. /// - public bool IsIdentity + public readonly bool IsIdentity { get { @@ -127,7 +127,7 @@ public bool IsIdentity /// public Vector3 Translation { - get + readonly get { return new Vector3(M41, M42, M43); } @@ -1260,7 +1260,7 @@ public static Matrix4x4 CreateReflection(Plane value) /// Calculates the determinant of the matrix. /// /// The determinant of the matrix. - public float GetDeterminant() + public readonly float GetDeterminant() { // | a b c d | | f g h | | e g h | | e f h | | e f g | // | e f g h | = a | j k l | - b | i k l | + c | i j l | - d | i j k | @@ -2179,20 +2179,20 @@ public static unsafe Matrix4x4 Lerp(Matrix4x4 matrix1, Matrix4x4 matrix2, float /// /// The matrix to compare this instance to. /// True if the matrices are equal; False otherwise. - public bool Equals(Matrix4x4 other) => this == other; + public readonly bool Equals(Matrix4x4 other) => this == other; /// /// Returns a boolean indicating whether the given Object is equal to this matrix instance. /// /// The Object to compare against. /// True if the Object is equal to this matrix; False otherwise. - public override bool Equals(object obj) => (obj is Matrix4x4 other) && (this == other); + public override readonly bool Equals(object obj) => (obj is Matrix4x4 other) && (this == other); /// /// Returns a String representing this matrix instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return string.Format(CultureInfo.CurrentCulture, "{{ {{M11:{0} M12:{1} M13:{2} M14:{3}}} {{M21:{4} M22:{5} M23:{6} M24:{7}}} {{M31:{8} M32:{9} M33:{10} M34:{11}}} {{M41:{12} M42:{13} M43:{14} M44:{15}}} }}", M11, M12, M13, M14, @@ -2205,7 +2205,7 @@ public override string ToString() /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { unchecked { diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Plane.cs b/src/System.Numerics.Vectors/src/System/Numerics/Plane.cs index 716e6f53430a..65b43604796c 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Plane.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Plane.cs @@ -314,7 +314,7 @@ public static float DotNormal(Plane plane, Vector3 value) /// The Plane to compare this instance to. /// True if the other Plane is equal to this instance; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(Plane other) + public readonly bool Equals(Plane other) { if (Vector.IsHardwareAccelerated) { @@ -335,7 +335,7 @@ public bool Equals(Plane other) /// The Object to compare against. /// True if the Object is equal to this Plane; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Plane) { @@ -349,7 +349,7 @@ public override bool Equals(object obj) /// Returns a String representing this Plane instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { CultureInfo ci = CultureInfo.CurrentCulture; @@ -360,7 +360,7 @@ public override string ToString() /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { return Normal.GetHashCode() + D.GetHashCode(); } diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Quaternion.cs b/src/System.Numerics.Vectors/src/System/Numerics/Quaternion.cs index 74c3116b692f..dd9ba1ae1b55 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Quaternion.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Quaternion.cs @@ -42,7 +42,7 @@ public static Quaternion Identity /// /// Returns whether the Quaternion is the identity Quaternion. /// - public bool IsIdentity + public readonly bool IsIdentity { get { return X == 0f && Y == 0f && Z == 0f && W == 1f; } } @@ -79,7 +79,7 @@ public Quaternion(Vector3 vectorPart, float scalarPart) /// Calculates the length of the Quaternion. /// /// The computed length of the Quaternion. - public float Length() + public readonly float Length() { float ls = X * X + Y * Y + Z * Z + W * W; @@ -90,7 +90,7 @@ public float Length() /// Calculates the length squared of the Quaternion. This operation is cheaper than Length(). /// /// The length squared of the Quaternion. - public float LengthSquared() + public readonly float LengthSquared() { return X * X + Y * Y + Z * Z + W * W; } @@ -748,7 +748,7 @@ public static Quaternion Divide(Quaternion value1, Quaternion value2) /// /// The Quaternion to compare this instance to. /// True if the other Quaternion is equal to this instance; False otherwise. - public bool Equals(Quaternion other) + public readonly bool Equals(Quaternion other) { return (X == other.X && Y == other.Y && @@ -761,7 +761,7 @@ public bool Equals(Quaternion other) /// /// The Object to compare against. /// True if the Object is equal to this Quaternion; False otherwise. - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Quaternion) { @@ -775,7 +775,7 @@ public override bool Equals(object obj) /// Returns a String representing this Quaternion instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return string.Format(CultureInfo.CurrentCulture, "{{X:{0} Y:{1} Z:{2} W:{3}}}", X, Y, Z, W); } @@ -784,7 +784,7 @@ public override string ToString() /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { return unchecked(X.GetHashCode() + Y.GetHashCode() + Z.GetHashCode() + W.GetHashCode()); } diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector2.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector2.cs index c6eec13a15b6..ac195d640067 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector2.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector2.cs @@ -52,7 +52,7 @@ public static Vector2 One /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { int hash = this.X.GetHashCode(); hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); @@ -65,7 +65,7 @@ public override int GetHashCode() /// The Object to compare against. /// True if the Object is equal to this Vector2; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (!(obj is Vector2)) return false; @@ -76,7 +76,7 @@ public override bool Equals(object obj) /// Returns a String representing this Vector2 instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return ToString("G", CultureInfo.CurrentCulture); } @@ -86,7 +86,7 @@ public override string ToString() /// /// The format of individual elements. /// The string representation. - public string ToString(string format) + public readonly string ToString(string format) { return ToString(format, CultureInfo.CurrentCulture); } @@ -98,7 +98,7 @@ public string ToString(string format) /// The format of individual elements. /// The format provider to use when formatting elements. /// The string representation. - public string ToString(string format, IFormatProvider formatProvider) + public readonly string ToString(string format, IFormatProvider formatProvider) { StringBuilder sb = new StringBuilder(); string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; @@ -116,7 +116,7 @@ public string ToString(string format, IFormatProvider formatProvider) /// /// The vector's length. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Length() + public readonly float Length() { if (Vector.IsHardwareAccelerated) { @@ -135,7 +135,7 @@ public float Length() /// /// The vector's length squared. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float LengthSquared() + public readonly float LengthSquared() { if (Vector.IsHardwareAccelerated) { diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector2_Intrinsics.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector2_Intrinsics.cs index 6836c21155b6..1bf66db1bb34 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector2_Intrinsics.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector2_Intrinsics.cs @@ -50,7 +50,7 @@ public Vector2(float x, float y) /// The destination array. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(float[] array) + public readonly void CopyTo(float[] array) { CopyTo(array, 0); } @@ -64,7 +64,7 @@ public void CopyTo(float[] array) /// If number of elements in source vector is greater than those available in destination array /// or if there are not enough elements to copy. [Intrinsic] - public void CopyTo(float[] array, int index) + public readonly void CopyTo(float[] array, int index) { if (array == null) { @@ -89,7 +89,7 @@ public void CopyTo(float[] array, int index) /// The Vector2 to compare this instance to. /// True if the other Vector2 is equal to this instance; False otherwise. [Intrinsic] - public bool Equals(Vector2 other) + public readonly bool Equals(Vector2 other) { return this.X == other.X && this.Y == other.Y; } diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector3.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector3.cs index bd8ae0b4aa10..105fce667207 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector3.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector3.cs @@ -57,7 +57,7 @@ public static Vector3 One /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { int hash = this.X.GetHashCode(); hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); @@ -71,7 +71,7 @@ public override int GetHashCode() /// The Object to compare against. /// True if the Object is equal to this Vector3; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (!(obj is Vector3)) return false; @@ -82,7 +82,7 @@ public override bool Equals(object obj) /// Returns a String representing this Vector3 instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return ToString("G", CultureInfo.CurrentCulture); } @@ -92,7 +92,7 @@ public override string ToString() /// /// The format of individual elements. /// The string representation. - public string ToString(string format) + public readonly string ToString(string format) { return ToString(format, CultureInfo.CurrentCulture); } @@ -104,7 +104,7 @@ public string ToString(string format) /// The format of individual elements. /// The format provider to use when formatting elements. /// The string representation. - public string ToString(string format, IFormatProvider formatProvider) + public readonly string ToString(string format, IFormatProvider formatProvider) { StringBuilder sb = new StringBuilder(); string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; @@ -125,7 +125,7 @@ public string ToString(string format, IFormatProvider formatProvider) /// /// The vector's length. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Length() + public readonly float Length() { if (Vector.IsHardwareAccelerated) { @@ -144,7 +144,7 @@ public float Length() /// /// The vector's length squared. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float LengthSquared() + public readonly float LengthSquared() { if (Vector.IsHardwareAccelerated) { diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector3_Intrinsics.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector3_Intrinsics.cs index 5bfff04cfd0b..c41baa46aa8f 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector3_Intrinsics.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector3_Intrinsics.cs @@ -64,7 +64,7 @@ public Vector3(float x, float y, float z) /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(float[] array) + public readonly void CopyTo(float[] array) { CopyTo(array, 0); } @@ -78,7 +78,7 @@ public void CopyTo(float[] array) /// If number of elements in source vector is greater than those available in destination array. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(float[] array, int index) + public readonly void CopyTo(float[] array, int index) { if (array == null) { @@ -104,7 +104,7 @@ public void CopyTo(float[] array, int index) /// The Vector3 to compare this instance to. /// True if the other Vector3 is equal to this instance; False otherwise. [Intrinsic] - public bool Equals(Vector3 other) + public readonly bool Equals(Vector3 other) { return X == other.X && Y == other.Y && diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector4.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector4.cs index 32c26698035f..a65b72addcf0 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector4.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector4.cs @@ -60,7 +60,7 @@ public static Vector4 One /// Returns the hash code for this instance. /// /// The hash code. - public override int GetHashCode() + public override readonly int GetHashCode() { int hash = this.X.GetHashCode(); hash = HashHelpers.Combine(hash, this.Y.GetHashCode()); @@ -75,7 +75,7 @@ public override int GetHashCode() /// The Object to compare against. /// True if the Object is equal to this Vector4; False otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (!(obj is Vector4)) return false; @@ -86,7 +86,7 @@ public override bool Equals(object obj) /// Returns a String representing this Vector4 instance. /// /// The string representation. - public override string ToString() + public override readonly string ToString() { return ToString("G", CultureInfo.CurrentCulture); } @@ -96,7 +96,7 @@ public override string ToString() /// /// The format of individual elements. /// The string representation. - public string ToString(string format) + public readonly string ToString(string format) { return ToString(format, CultureInfo.CurrentCulture); } @@ -108,7 +108,7 @@ public string ToString(string format) /// The format of individual elements. /// The format provider to use when formatting elements. /// The string representation. - public string ToString(string format, IFormatProvider formatProvider) + public readonly string ToString(string format, IFormatProvider formatProvider) { StringBuilder sb = new StringBuilder(); string separator = NumberFormatInfo.GetInstance(formatProvider).NumberGroupSeparator; @@ -132,7 +132,7 @@ public string ToString(string format, IFormatProvider formatProvider) /// /// The vector's length. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float Length() + public readonly float Length() { if (Vector.IsHardwareAccelerated) { @@ -152,7 +152,7 @@ public float Length() /// /// The vector's length squared. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float LengthSquared() + public readonly float LengthSquared() { if (Vector.IsHardwareAccelerated) { diff --git a/src/System.Numerics.Vectors/src/System/Numerics/Vector4_Intrinsics.cs b/src/System.Numerics.Vectors/src/System/Numerics/Vector4_Intrinsics.cs index 79c6f5eb7e64..4c57fb7d6e6d 100644 --- a/src/System.Numerics.Vectors/src/System/Numerics/Vector4_Intrinsics.cs +++ b/src/System.Numerics.Vectors/src/System/Numerics/Vector4_Intrinsics.cs @@ -93,7 +93,7 @@ public Vector4(Vector3 value, float w) /// [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(float[] array) + public readonly void CopyTo(float[] array) { CopyTo(array, 0); } @@ -107,7 +107,7 @@ public void CopyTo(float[] array) /// If number of elements in source vector is greater than those available in destination array. [Intrinsic] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyTo(float[] array, int index) + public readonly void CopyTo(float[] array, int index) { if (array == null) { @@ -134,7 +134,7 @@ public void CopyTo(float[] array, int index) /// The Vector4 to compare this instance to. /// True if the other Vector4 is equal to this instance; False otherwise. [Intrinsic] - public bool Equals(Vector4 other) + public readonly bool Equals(Vector4 other) { return this.X == other.X && this.Y == other.Y diff --git a/src/System.Private.DataContractSerialization/src/Configurations.props b/src/System.Private.DataContractSerialization/src/Configurations.props index c5bd911e4d6e..e91b20f7fc15 100644 --- a/src/System.Private.DataContractSerialization/src/Configurations.props +++ b/src/System.Private.DataContractSerialization/src/Configurations.props @@ -2,7 +2,6 @@ netcoreapp; - netcoreappaot; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj b/src/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj index 267fb733f900..1036800e1474 100644 --- a/src/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj +++ b/src/System.Private.DataContractSerialization/src/System.Private.DataContractSerialization.csproj @@ -12,7 +12,7 @@ $(DefineConstants);uapaot false - netcoreapp-Debug;netcoreapp-Release;netcoreappaot-Debug;netcoreappaot-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release System\Runtime\Serialization diff --git a/src/System.Private.Reflection.Metadata.Ecma335/System.Private.Reflection.Metadata.Ecma335.sln b/src/System.Private.Reflection.Metadata.Ecma335/System.Private.Reflection.Metadata.Ecma335.sln index 2f58de059b13..62c84a527714 100644 --- a/src/System.Private.Reflection.Metadata.Ecma335/System.Private.Reflection.Metadata.Ecma335.sln +++ b/src/System.Private.Reflection.Metadata.Ecma335/System.Private.Reflection.Metadata.Ecma335.sln @@ -12,10 +12,10 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Debug|Any CPU.ActiveCfg = netcoreappaot-Windows_NT-Debug|Any CPU - {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Debug|Any CPU.Build.0 = netcoreappaot-Windows_NT-Debug|Any CPU - {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Release|Any CPU.ActiveCfg = netcoreappaot-Windows_NT-Release|Any CPU - {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Release|Any CPU.Build.0 = netcoreappaot-Windows_NT-Release|Any CPU + {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Debug|Any CPU.ActiveCfg = uapaot-Windows_NT-Debug|Any CPU + {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Debug|Any CPU.Build.0 = uapaot-Windows_NT-Debug|Any CPU + {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Release|Any CPU.ActiveCfg = uapaot-Windows_NT-Release|Any CPU + {3A8B2CAD-6FFD-4A37-A53A-0F2D5EF99164}.Release|Any CPU.Build.0 = uapaot-Windows_NT-Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/System.Private.Reflection.Metadata.Ecma335/src/Configurations.props b/src/System.Private.Reflection.Metadata.Ecma335/src/Configurations.props index 4e0b55a64cc3..bcd1f4012035 100644 --- a/src/System.Private.Reflection.Metadata.Ecma335/src/Configurations.props +++ b/src/System.Private.Reflection.Metadata.Ecma335/src/Configurations.props @@ -2,8 +2,6 @@ uapaot-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; diff --git a/src/System.Private.Reflection.Metadata.Ecma335/src/System.Private.Reflection.Metadata.Ecma335.csproj b/src/System.Private.Reflection.Metadata.Ecma335/src/System.Private.Reflection.Metadata.Ecma335.csproj index e16cd23085b5..51f53c654b12 100644 --- a/src/System.Private.Reflection.Metadata.Ecma335/src/System.Private.Reflection.Metadata.Ecma335.csproj +++ b/src/System.Private.Reflection.Metadata.Ecma335/src/System.Private.Reflection.Metadata.Ecma335.csproj @@ -10,7 +10,7 @@ $(NoWarn);3021 $(DefineConstants);CORERT ..\..\System.Reflection.Metadata\src\ - netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Private.Uri/src/Configurations.props b/src/System.Private.Uri/src/Configurations.props index bb83bc28c246..dfdd4746cdb0 100644 --- a/src/System.Private.Uri/src/Configurations.props +++ b/src/System.Private.Uri/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; netcoreapp-Unix; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; diff --git a/src/System.Private.Uri/src/System.Private.Uri.csproj b/src/System.Private.Uri/src/System.Private.Uri.csproj index 3613515a31ad..f54ec34f5953 100644 --- a/src/System.Private.Uri/src/System.Private.Uri.csproj +++ b/src/System.Private.Uri/src/System.Private.Uri.csproj @@ -3,7 +3,7 @@ {4AC5343E-6E31-4BA5-A795-0493AE7E9008} System.Private.Uri true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Private.Xml.Linq/src/Configurations.props b/src/System.Private.Xml.Linq/src/Configurations.props index 95579245fb29..9658dd322cf9 100644 --- a/src/System.Private.Xml.Linq/src/Configurations.props +++ b/src/System.Private.Xml.Linq/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; netcoreapp-Unix; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; diff --git a/src/System.Private.Xml/src/Configurations.props b/src/System.Private.Xml/src/Configurations.props index 5d1182962c9e..81407aeeeac8 100644 --- a/src/System.Private.Xml/src/Configurations.props +++ b/src/System.Private.Xml/src/Configurations.props @@ -2,8 +2,6 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Private.Xml/src/System.Private.Xml.csproj b/src/System.Private.Xml/src/System.Private.Xml.csproj index 1ffd334503fd..1fd217ea00c3 100644 --- a/src/System.Private.Xml/src/System.Private.Xml.csproj +++ b/src/System.Private.Xml/src/System.Private.Xml.csproj @@ -5,10 +5,10 @@ System.Xml true $(NoWarn);CS1573;649;169;414 - true + true $(DefineConstants);FEATURE_COMPILED_XSL $(DefineConstants);FEATURE_SERIALIZATION_UAPAOT;UAPAOT - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Private.Xml/src/System/Xml/IApplicationResourceStreamResolver.cs b/src/System.Private.Xml/src/System/Xml/IApplicationResourceStreamResolver.cs index e3402f6f10ca..ce284aa2dd3f 100644 --- a/src/System.Private.Xml/src/System/Xml/IApplicationResourceStreamResolver.cs +++ b/src/System.Private.Xml/src/System/Xml/IApplicationResourceStreamResolver.cs @@ -8,7 +8,8 @@ namespace System.Xml { - [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)] + // we must specify the error flag as false so that we can typeforward this type without hitting a compile error. + [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", false)] [EditorBrowsable(EditorBrowsableState.Never)] public interface IApplicationResourceStreamResolver { diff --git a/src/System.Reflection.Emit.ILGeneration/src/Configurations.props b/src/System.Reflection.Emit.ILGeneration/src/Configurations.props index 0b9482c33258..4482a77069e8 100644 --- a/src/System.Reflection.Emit.ILGeneration/src/Configurations.props +++ b/src/System.Reflection.Emit.ILGeneration/src/Configurations.props @@ -11,8 +11,6 @@ netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; $(PackageConfigurations); diff --git a/src/System.Reflection.Emit.Lightweight/src/Configurations.props b/src/System.Reflection.Emit.Lightweight/src/Configurations.props index a8692c188781..4482a77069e8 100644 --- a/src/System.Reflection.Emit.Lightweight/src/Configurations.props +++ b/src/System.Reflection.Emit.Lightweight/src/Configurations.props @@ -9,8 +9,6 @@ netcoreapp-Windows_NT; netcoreapp-Unix; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; uapaot-Windows_NT; $(PackageConfigurations); diff --git a/src/System.Reflection.Emit/src/Configurations.props b/src/System.Reflection.Emit/src/Configurations.props index e40b174731bd..9db8681fd21e 100644 --- a/src/System.Reflection.Emit/src/Configurations.props +++ b/src/System.Reflection.Emit/src/Configurations.props @@ -9,8 +9,6 @@ netcoreapp-Windows_NT; netcoreapp-Unix; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uap-Windows_NT; uapaot-Windows_NT; $(PackageConfigurations); diff --git a/src/System.Reflection.Extensions/src/Configurations.props b/src/System.Reflection.Extensions/src/Configurations.props index 7dc2eed8d4e9..f9e1d87695a8 100644 --- a/src/System.Reflection.Extensions/src/Configurations.props +++ b/src/System.Reflection.Extensions/src/Configurations.props @@ -4,7 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp; - netcoreappaot; diff --git a/src/System.Reflection.Extensions/src/System.Reflection.Extensions.csproj b/src/System.Reflection.Extensions/src/System.Reflection.Extensions.csproj index 327a9ac692bd..d7e5701260d7 100644 --- a/src/System.Reflection.Extensions/src/System.Reflection.Extensions.csproj +++ b/src/System.Reflection.Extensions/src/System.Reflection.Extensions.csproj @@ -3,7 +3,7 @@ System.Reflection.Extensions true {26699AB1-DBAC-4819-A1C0-185AB37CF127} - netcoreapp-Debug;netcoreapp-Release;netcoreappaot-Debug;netcoreappaot-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/NamespaceCache.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/NamespaceCache.cs index 34993b3fb722..85dfbf2ba767 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/NamespaceCache.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Internal/NamespaceCache.cs @@ -13,7 +13,7 @@ internal class NamespaceCache { private readonly MetadataReader _metadataReader; private readonly object _namespaceTableAndListLock = new object(); - private Dictionary _namespaceTable; + private volatile Dictionary _namespaceTable; private NamespaceData _rootNamespace; private uint _virtualNamespaceCounter; @@ -154,8 +154,8 @@ private void PopulateNamespaceTable() } } - _namespaceTable = namespaceTable; _rootNamespace = namespaceTable[rootNamespace]; + _namespaceTable = namespaceTable; } } diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs index 6665314d7317..4c20e9d6c66b 100644 --- a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs @@ -492,7 +492,16 @@ public void GetOrAddDocumentName2() Assert.Equal(@"a/", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n6)))); Assert.Equal(@"/", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n7)))); Assert.Equal(@"\\", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n8)))); - Assert.Equal("\uFFFd\uFFFd", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n9)))); + if (PlatformDetection.IsNetCore) + { + Assert.Equal("\uFFFD\uFFFD\uFFFD", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n9)))); + } + else + { + // Versions of .NET prior to Core 3.0 didn't follow Unicode recommendations for U+FFFD substitution, + // so they sometimes emitted too few replacement chars. + Assert.Equal("\uFFFD\uFFFD", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n9)))); + } Assert.Equal("\0", mdReader.GetString(MetadataTokens.DocumentNameBlobHandle(MetadataTokens.GetHeapOffset(n10)))); } } diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataRootBuilderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataRootBuilderTests.cs index b6c05bce30ee..09220310e483 100644 --- a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataRootBuilderTests.cs +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataRootBuilderTests.cs @@ -377,11 +377,24 @@ public void MetadataVersion() 0x08, 0x00, 0x00, 0x00, // padded version: + // [ E1 88 B4 ] -> U+1234 + // [ ED ] -> invalid (ED cannot be followed by A0) -> U+FFFD + // [ A0 ] -> invalid (not ASCII, not valid leading byte) -> U+FFFD + // [ 80 ] -> invalid (not ASCII, not valid leading byte) -> U+FFFD 0xE1, 0x88, 0xB4, 0xED, 0xA0, 0x80, 0x00, 0x00, }, builder.Slice(12, -132)); // the default decoder replaces bad byte sequences by U+FFFD - Assert.Equal("\u1234\ufffd\ufffd", ReadVersion(builder)); + if (PlatformDetection.IsNetCore) + { + Assert.Equal("\u1234\ufffd\ufffd\ufffd", ReadVersion(builder)); + } + else + { + // Versions of .NET prior to Core 3.0 didn't follow Unicode recommendations for U+FFFD substitution, + // so they sometimes emitted too few replacement chars. + Assert.Equal("\u1234\ufffd\ufffd", ReadVersion(builder)); + } } } } diff --git a/src/System.Reflection.Primitives/src/Configurations.props b/src/System.Reflection.Primitives/src/Configurations.props index 8e16bc6010c4..a652fc326205 100644 --- a/src/System.Reflection.Primitives/src/Configurations.props +++ b/src/System.Reflection.Primitives/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Reflection.Primitives/src/System.Reflection.Primitives.csproj b/src/System.Reflection.Primitives/src/System.Reflection.Primitives.csproj index 383e1faccbca..bf5ea1ac471c 100644 --- a/src/System.Reflection.Primitives/src/System.Reflection.Primitives.csproj +++ b/src/System.Reflection.Primitives/src/System.Reflection.Primitives.csproj @@ -3,7 +3,7 @@ System.Reflection.Primitives true {CCE47F37-2C51-44EB-8F54-0474D7831515} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Reflection.TypeExtensions/src/Configurations.props b/src/System.Reflection.TypeExtensions/src/Configurations.props index 65b28789980d..6e672ff37987 100644 --- a/src/System.Reflection.TypeExtensions/src/Configurations.props +++ b/src/System.Reflection.TypeExtensions/src/Configurations.props @@ -10,8 +10,6 @@ $(PackageConfigurations); netfx; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj b/src/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj index 98f19f53d743..7a6ff1ca8940 100644 --- a/src/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj +++ b/src/System.Reflection.TypeExtensions/src/System.Reflection.TypeExtensions.csproj @@ -3,7 +3,7 @@ System.Reflection.TypeExtensions true {A9EF5E88-1AD9-4545-8AFE-CA0F5F00E2CB} - net461-Debug;net461-Release;netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release;netstandard1.3-Debug;netstandard1.3-Release;netstandard1.5-Debug;netstandard1.5-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + net461-Debug;net461-Release;netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release;netstandard1.3-Debug;netstandard1.3-Release;netstandard1.5-Debug;netstandard1.5-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release SR.PlatformNotSupported_ReflectionTypeExtensions @@ -16,7 +16,7 @@ - + diff --git a/src/System.Reflection/src/Configurations.props b/src/System.Reflection/src/Configurations.props index 8e16bc6010c4..a652fc326205 100644 --- a/src/System.Reflection/src/Configurations.props +++ b/src/System.Reflection/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Reflection/src/System.Reflection.csproj b/src/System.Reflection/src/System.Reflection.csproj index 6ae3b9a2d502..8ba7f002e4f1 100644 --- a/src/System.Reflection/src/System.Reflection.csproj +++ b/src/System.Reflection/src/System.Reflection.csproj @@ -3,7 +3,7 @@ System.Reflection true {68F87E68-E13F-4354-A6D6-B44727FE53EE} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Reflection/tests/AssemblyTests.cs b/src/System.Reflection/tests/AssemblyTests.cs index f394ca54231f..f06eea974cc1 100644 --- a/src/System.Reflection/tests/AssemblyTests.cs +++ b/src/System.Reflection/tests/AssemblyTests.cs @@ -465,6 +465,16 @@ public void LoadWithPartialName() string simpleName = typeof(AssemblyTests).Assembly.GetName().Name; var assembly = Assembly.LoadWithPartialName(simpleName); Assert.Equal(typeof(AssemblyTests).Assembly, assembly); + } + + [Fact] + [SkipOnTargetFramework(TargetFrameworkMonikers.UapAot, "Assembly.LoadFromWithPartialName() not supported on UapAot")] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + public void LoadWithPartialName_Neg() + { + AssertExtensions.Throws("partialName", () => Assembly.LoadWithPartialName(null)); + AssertExtensions.Throws("partialName", () => Assembly.LoadWithPartialName("")); + Assert.Null(Assembly.LoadWithPartialName("no such assembly")); } #pragma warning restore 618 @@ -680,6 +690,8 @@ public void AssemblyLoadFromStringNeg() string emptyCName = new string('\0', 1); AssertExtensions.Throws(null, () => Assembly.Load(emptyCName)); + + Assert.Throws(() => Assembly.Load("no such assembly")); // No such assembly } [Fact] diff --git a/src/System.Reflection/tests/TypeInfoTests.cs b/src/System.Reflection/tests/TypeInfoTests.cs index 61bdd94d7796..7109e6e74ff9 100644 --- a/src/System.Reflection/tests/TypeInfoTests.cs +++ b/src/System.Reflection/tests/TypeInfoTests.cs @@ -984,8 +984,8 @@ public void GetElementType(Type type, Type expected) Assert.Equal(expected, type.GetTypeInfo().GetElementType()); } - [Theory] - public void GenericParameterConstraints(Type type) + [Fact] + public void GenericParameterConstraints() { Type[] genericTypeParameters = typeof(MethodClassWithConstraints<,>).GetTypeInfo().GenericTypeParameters; Assert.Equal(2, genericTypeParameters.Length); diff --git a/src/System.Resources.ResourceManager/src/Configurations.props b/src/System.Resources.ResourceManager/src/Configurations.props index 8e16bc6010c4..a652fc326205 100644 --- a/src/System.Resources.ResourceManager/src/Configurations.props +++ b/src/System.Resources.ResourceManager/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Resources.ResourceManager/src/System.Resources.ResourceManager.csproj b/src/System.Resources.ResourceManager/src/System.Resources.ResourceManager.csproj index 669ce9a94eb0..4595290640fb 100644 --- a/src/System.Resources.ResourceManager/src/System.Resources.ResourceManager.csproj +++ b/src/System.Resources.ResourceManager/src/System.Resources.ResourceManager.csproj @@ -3,7 +3,7 @@ System.Resources.ResourceManager true {3E7810B2-2802-4867-B223-30427F3F2D5B} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Runtime.Extensions/src/Configurations.props b/src/System.Runtime.Extensions/src/Configurations.props index 29e6e7bc90d4..0432807ad1bc 100644 --- a/src/System.Runtime.Extensions/src/Configurations.props +++ b/src/System.Runtime.Extensions/src/Configurations.props @@ -3,8 +3,6 @@ netcoreapp-Unix; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; uapaot-Windows_NT; uap-Windows_NT; diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index bd1cc5ef33e5..5aa1156a7162 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -7,7 +7,7 @@ true true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Runtime.Extensions/tests/System/Math.cs b/src/System.Runtime.Extensions/tests/System/Math.cs index c066a1f8e0fc..c7ff12f1d066 100644 --- a/src/System.Runtime.Extensions/tests/System/Math.cs +++ b/src/System.Runtime.Extensions/tests/System/Math.cs @@ -1080,7 +1080,7 @@ public static void Max_Double_NetFramework() [InlineData(-0.0, 0.0, 0.0)] [InlineData(2.0, -3.0, 2.0)] [InlineData(3.0, -2.0, 3.0)] - [InlineData(double.PositiveInfinity, double.NaN, double.PositiveInfinity)] + [InlineData(double.PositiveInfinity, double.NaN, double.NaN)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public static void Max_Double_NotNetFramework(double x, double y, double expectedResult) { @@ -1133,7 +1133,7 @@ public static void Max_Single_NetFramework() [InlineData(-0.0f, 0.0f, 0.0f)] [InlineData(2.0f, -3.0f, 2.0f)] [InlineData(3.0f, -2.0f, 3.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public static void Max_Single_NotNetFramework(float x, float y, float expectedResult) { @@ -1193,7 +1193,7 @@ public static void Min_Double_NetFramework() [InlineData(-0.0, 0.0, -0.0)] [InlineData(2.0, -3.0, -3.0)] [InlineData(3.0, -2.0, -2.0)] - [InlineData(double.PositiveInfinity, double.NaN, double.PositiveInfinity)] + [InlineData(double.PositiveInfinity, double.NaN, double.NaN)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public static void Min_Double_NotNetFramework(double x, double y, double expectedResult) { @@ -1246,7 +1246,7 @@ public static void Min_Single_NetFramework() [InlineData(-0.0f, 0.0f, -0.0f)] [InlineData(2.0f, -3.0f, -3.0f)] [InlineData(3.0f, -2.0f, -2.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] public static void Min_Single_NotNetFramework(float x, float y, float expectedResult) { diff --git a/src/System.Runtime.Extensions/tests/System/MathF.netcoreapp.cs b/src/System.Runtime.Extensions/tests/System/MathF.netcoreapp.cs index 905ebf9de7aa..89836e9c3a05 100644 --- a/src/System.Runtime.Extensions/tests/System/MathF.netcoreapp.cs +++ b/src/System.Runtime.Extensions/tests/System/MathF.netcoreapp.cs @@ -1208,7 +1208,7 @@ public static void Log10(float value, float expectedResult, float allowedVarianc [InlineData(-0.0f, 0.0f, 0.0f)] [InlineData(2.0f, -3.0f, 2.0f)] [InlineData(3.0f, -2.0f, 3.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] public static void Max(float x, float y, float expectedResult) { AssertEqual(expectedResult, MathF.Max(x, y), 0.0f); @@ -1221,7 +1221,7 @@ public static void Max(float x, float y, float expectedResult) [InlineData(-0.0f, 0.0f, 0.0f)] [InlineData(2.0f, -3.0f, -3.0f)] [InlineData(3.0f, -2.0f, 3.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] public static void MaxMagnitude(float x, float y, float expectedResult) { AssertEqual(expectedResult, MathF.MaxMagnitude(x, y), 0.0f); @@ -1234,7 +1234,7 @@ public static void MaxMagnitude(float x, float y, float expectedResult) [InlineData(-0.0f, 0.0f, -0.0f)] [InlineData(2.0f, -3.0f, -3.0f)] [InlineData(3.0f, -2.0f, -2.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] public static void Min(float x, float y, float expectedResult) { AssertEqual(expectedResult, MathF.Min(x, y), 0.0f); @@ -1247,7 +1247,7 @@ public static void Min(float x, float y, float expectedResult) [InlineData(-0.0f, 0.0f, -0.0f)] [InlineData(2.0f, -3.0f, 2.0f)] [InlineData(3.0f, -2.0f, -2.0f)] - [InlineData(float.PositiveInfinity, float.NaN, float.PositiveInfinity)] + [InlineData(float.PositiveInfinity, float.NaN, float.NaN)] public static void MinMagnitude(float x, float y, float expectedResult) { AssertEqual(expectedResult, MathF.MinMagnitude(x, y), 0.0f); diff --git a/src/System.Runtime.Extensions/tests/System/MathTests.netcoreapp.cs b/src/System.Runtime.Extensions/tests/System/MathTests.netcoreapp.cs index 682c8a904364..71c5de3e3398 100644 --- a/src/System.Runtime.Extensions/tests/System/MathTests.netcoreapp.cs +++ b/src/System.Runtime.Extensions/tests/System/MathTests.netcoreapp.cs @@ -599,7 +599,7 @@ public static void Log2(double value, double expectedResult, double allowedVaria [InlineData(-0.0, 0.0, 0.0)] [InlineData(2.0, -3.0, -3.0)] [InlineData(3.0, -2.0, 3.0)] - [InlineData(double.PositiveInfinity, double.NaN, double.PositiveInfinity)] + [InlineData(double.PositiveInfinity, double.NaN, double.NaN)] public static void MaxMagnitude(double x, double y, double expectedResult) { AssertEqual(expectedResult, Math.MaxMagnitude(x, y), 0.0); @@ -612,7 +612,7 @@ public static void MaxMagnitude(double x, double y, double expectedResult) [InlineData(-0.0, 0.0, -0.0)] [InlineData(2.0, -3.0, 2.0)] [InlineData(3.0, -2.0, -2.0)] - [InlineData(double.PositiveInfinity, double.NaN, double.PositiveInfinity)] + [InlineData(double.PositiveInfinity, double.NaN, double.NaN)] public static void MinMagnitude(double x, double y, double expectedResult) { AssertEqual(expectedResult, Math.MinMagnitude(x, y), 0.0); diff --git a/src/System.Runtime.InteropServices/src/Configurations.props b/src/System.Runtime.InteropServices/src/Configurations.props index 1a0eb0f96e3f..7b3c7107b60e 100644 --- a/src/System.Runtime.InteropServices/src/Configurations.props +++ b/src/System.Runtime.InteropServices/src/Configurations.props @@ -3,8 +3,6 @@ uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; diff --git a/src/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj b/src/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj index 655d2e0b3526..1c3011be333c 100644 --- a/src/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj +++ b/src/System.Runtime.InteropServices/src/System.Runtime.InteropServices.csproj @@ -7,7 +7,7 @@ true $(NoWarn);0436;3001 true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release @@ -19,28 +19,48 @@ Common\Interop\Windows\Ole32\Interop.CoGetStandardMarshal.cs - - - - - - - - - - + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/Attributes.cs b/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IDispatchConstantAttribute.cs similarity index 54% rename from src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/Attributes.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IDispatchConstantAttribute.cs index 0217ea33bb33..89338b705987 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/Attributes.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IDispatchConstantAttribute.cs @@ -6,15 +6,7 @@ namespace System.Runtime.CompilerServices { - [System.AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] - public sealed partial class IUnknownConstantAttribute : CustomConstantAttribute - { - public IUnknownConstantAttribute() { } - - public override object Value => new UnknownWrapper(null); - } - - [System.AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] public sealed partial class IDispatchConstantAttribute : CustomConstantAttribute { public IDispatchConstantAttribute() { } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IUnknownConstantAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IUnknownConstantAttribute.cs new file mode 100644 index 000000000000..7b0f4b45160d --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/CompilerServices/IUnknownConstantAttribute.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. +// See the LICENSE file in the project root for more information. + +using System.Runtime.InteropServices; + +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter, Inherited = false)] + public sealed partial class IUnknownConstantAttribute : CustomConstantAttribute + { + public IUnknownConstantAttribute() { } + + public override object Value => new UnknownWrapper(null); + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AssemblyRegistrationFlags.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AssemblyRegistrationFlags.cs index 015f1f433cde..d2b9b453fe79 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AssemblyRegistrationFlags.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AssemblyRegistrationFlags.cs @@ -2,8 +2,6 @@ // 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; - namespace System.Runtime.InteropServices { [Flags] diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Attributes.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Attributes.cs deleted file mode 100644 index f58992a90abd..000000000000 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Attributes.cs +++ /dev/null @@ -1,242 +0,0 @@ -// 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. - -namespace System.Runtime.InteropServices -{ - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] - public sealed class AutomationProxyAttribute : Attribute - { - public AutomationProxyAttribute(bool val) => Value = val; - - public bool Value { get; } - } - - [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] - public sealed class ComAliasNameAttribute : Attribute - { - public ComAliasNameAttribute(string alias) => Value = alias; - - public string Value { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] - public sealed class ComCompatibleVersionAttribute : Attribute - { - public ComCompatibleVersionAttribute(int major, int minor, int build, int revision) - { - MajorVersion = major; - MinorVersion = minor; - BuildNumber = build; - RevisionNumber = revision; - } - - public int MajorVersion { get; } - public int MinorVersion { get; } - public int BuildNumber { get; } - public int RevisionNumber { get; } - } - - [AttributeUsage(AttributeTargets.All, Inherited = false)] - public sealed class ComConversionLossAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - public sealed class ComRegisterFunctionAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - public sealed class ComUnregisterFunctionAttribute : Attribute - { - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, Inherited = false)] - [Obsolete("This attribute is deprecated and will be removed in a future version.", error: false)] - public sealed class IDispatchImplAttribute : Attribute - { - public IDispatchImplAttribute(short implType) : this((IDispatchImplType)implType) - { - } - - public IDispatchImplAttribute(IDispatchImplType implType) => Value = implType; - - public IDispatchImplType Value { get; } - } - - [Obsolete("The IDispatchImplAttribute is deprecated.", error: false)] - public enum IDispatchImplType - { - CompatibleImpl = 2, - InternalImpl = 1, - SystemDefinedImpl = 0, - } - - [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] - public sealed class ImportedFromTypeLibAttribute : Attribute - { - public ImportedFromTypeLibAttribute(string tlbFile) => Value = tlbFile; - - public string Value { get; } - } - - [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] - public sealed class ManagedToNativeComInteropStubAttribute : Attribute - { - public ManagedToNativeComInteropStubAttribute(Type classType, string methodName) - { - ClassType = classType; - MethodName = methodName; - } - - public Type ClassType { get; } - public string MethodName { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] - public sealed class PrimaryInteropAssemblyAttribute : Attribute - { - public PrimaryInteropAssemblyAttribute(int major, int minor) - { - MajorVersion = major; - MinorVersion = minor; - } - - public int MajorVersion { get; } - public int MinorVersion { get; } - } - - [Obsolete("This attribute has been deprecated. Application Domains no longer respect Activation Context boundaries in IDispatch calls.", error: false)] - [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] - public sealed class SetWin32ContextInIDispatchAttribute : Attribute - { - public SetWin32ContextInIDispatchAttribute() - { - } - } - - [AttributeUsage(AttributeTargets.Interface, Inherited = false)] - public sealed class TypeLibImportClassAttribute : Attribute - { - public TypeLibImportClassAttribute(Type importClass) => Value = importClass.ToString(); - - public string Value { get; } - } - - [Flags] - public enum TypeLibTypeFlags - { - FAppObject = 0x0001, - FCanCreate = 0x0002, - FLicensed = 0x0004, - FPreDeclId = 0x0008, - FHidden = 0x0010, - FControl = 0x0020, - FDual = 0x0040, - FNonExtensible = 0x0080, - FOleAutomation = 0x0100, - FRestricted = 0x0200, - FAggregatable = 0x0400, - FReplaceable = 0x0800, - FDispatchable = 0x1000, - FReverseBind = 0x2000, - } - - [Flags] - public enum TypeLibFuncFlags - { - FRestricted = 0x0001, - FSource = 0x0002, - FBindable = 0x0004, - FRequestEdit = 0x0008, - FDisplayBind = 0x0010, - FDefaultBind = 0x0020, - FHidden = 0x0040, - FUsesGetLastError = 0x0080, - FDefaultCollelem = 0x0100, - FUiDefault = 0x0200, - FNonBrowsable = 0x0400, - FReplaceable = 0x0800, - FImmediateBind = 0x1000, - } - - [Flags] - public enum TypeLibVarFlags - { - FReadOnly = 0x0001, - FSource = 0x0002, - FBindable = 0x0004, - FRequestEdit = 0x0008, - FDisplayBind = 0x0010, - FDefaultBind = 0x0020, - FHidden = 0x0040, - FRestricted = 0x0080, - FDefaultCollelem = 0x0100, - FUiDefault = 0x0200, - FNonBrowsable = 0x0400, - FReplaceable = 0x0800, - FImmediateBind = 0x1000, - } - - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Struct, Inherited = false)] - public sealed class TypeLibTypeAttribute : Attribute - { - public TypeLibTypeAttribute(TypeLibTypeFlags flags) - { - Value = flags; - } - - public TypeLibTypeAttribute(short flags) - { - Value = (TypeLibTypeFlags)flags; - } - - public TypeLibTypeFlags Value { get; } - } - - [AttributeUsage(AttributeTargets.Method, Inherited = false)] - public sealed class TypeLibFuncAttribute : Attribute - { - public TypeLibFuncAttribute(TypeLibFuncFlags flags) - { - Value = flags; - } - - public TypeLibFuncAttribute(short flags) - { - Value = (TypeLibFuncFlags)flags; - } - - public TypeLibFuncFlags Value { get; } - } - - [AttributeUsage(AttributeTargets.Field, Inherited = false)] - public sealed class TypeLibVarAttribute : Attribute - { - public TypeLibVarAttribute(TypeLibVarFlags flags) - { - Value = flags; - } - - public TypeLibVarAttribute(short flags) - { - Value = (TypeLibVarFlags)flags; - } - - public TypeLibVarFlags Value { get; } - } - - [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] - public sealed class TypeLibVersionAttribute : Attribute - { - public TypeLibVersionAttribute(int major, int minor) - { - MajorVersion = major; - MinorVersion = minor; - } - - public int MajorVersion { get; } - public int MinorVersion { get; } - } -} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AutomationProxyAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AutomationProxyAttribute.cs new file mode 100644 index 000000000000..c6847230a9e3 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/AutomationProxyAttribute.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] + public sealed class AutomationProxyAttribute : Attribute + { + public AutomationProxyAttribute(bool val) => Value = val; + + public bool Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComAliasNameAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComAliasNameAttribute.cs new file mode 100644 index 000000000000..96643634593e --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComAliasNameAttribute.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + public sealed class ComAliasNameAttribute : Attribute + { + public ComAliasNameAttribute(string alias) => Value = alias; + + public string Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComCompatibleVersionAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComCompatibleVersionAttribute.cs new file mode 100644 index 000000000000..a7e6a563aa4e --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComCompatibleVersionAttribute.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class ComCompatibleVersionAttribute : Attribute + { + public ComCompatibleVersionAttribute(int major, int minor, int build, int revision) + { + MajorVersion = major; + MinorVersion = minor; + BuildNumber = build; + RevisionNumber = revision; + } + + public int MajorVersion { get; } + public int MinorVersion { get; } + public int BuildNumber { get; } + public int RevisionNumber { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComConversionLossAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComConversionLossAttribute.cs new file mode 100644 index 000000000000..def2c3cc5b74 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComConversionLossAttribute.cs @@ -0,0 +1,11 @@ +// 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. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.All, Inherited = false)] + public sealed class ComConversionLossAttribute : Attribute + { + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComRegisterFunctionAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComRegisterFunctionAttribute.cs new file mode 100644 index 000000000000..f481e8cbb91a --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComRegisterFunctionAttribute.cs @@ -0,0 +1,11 @@ +// 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. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class ComRegisterFunctionAttribute : Attribute + { + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/advf.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/ADVF.cs similarity index 64% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/advf.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/ADVF.cs index dbadca35aca6..8cc1a1ee6cac 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/advf.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/ADVF.cs @@ -4,6 +4,11 @@ namespace System.Runtime.InteropServices.ComTypes { + /// + /// Note: ADVF_ONLYONCE and ADVF_PRIMEFIRST values conform with objidl.dll but are backwards from + /// the Platform SDK documentation as of 07/21/2003. + /// https://docs.microsoft.com/en-us/windows/desktop/api/objidl/ne-objidl-tagadvf. + /// [Flags] public enum ADVF { @@ -15,7 +20,4 @@ public enum ADVF ADVFCACHE_FORCEBUILTIN = 16, ADVFCACHE_ONSAVE = 32 } - // Note: ADVF_ONLYONCE and ADVF_PRIMEFIRST values conform with objidl.dll but are backwards from - // the Platform SDK documentation as of 07/21/2003. - // https://docs.microsoft.com/en-us/windows/desktop/api/objidl/ne-objidl-tagadvf. } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/datadir.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/DATADIR.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/datadir.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/DATADIR.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/dvaspect.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/DVASPECT.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/dvaspect.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/DVASPECT.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/formatetc.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/FORMATETC.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/formatetc.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/FORMATETC.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IAdviseSink.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IAdviseSink.cs index 77617f48fa7e..4c485d6cc866 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IAdviseSink.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IAdviseSink.cs @@ -4,51 +4,51 @@ namespace System.Runtime.InteropServices.ComTypes { - /// - /// The IAdviseSink interface enables containers and other objects to - /// receive notifications of data changes, view changes, and compound-document - /// changes occurring in objects of interest. Container applications, for - /// example, require such notifications to keep cached presentations of their - /// linked and embedded objects up-to-date. Calls to IAdviseSink methods are - /// asynchronous, so the call is sent and then the next instruction is executed - /// without waiting for the call's return. - /// + /// + /// The IAdviseSink interface enables containers and other objects to + /// receive notifications of data changes, view changes, and compound-document + /// changes occurring in objects of interest. Container applications, for + /// example, require such notifications to keep cached presentations of their + /// linked and embedded objects up-to-date. Calls to IAdviseSink methods are + /// asynchronous, so the call is sent and then the next instruction is executed + /// without waiting for the call's return. + /// [ComImport] [Guid("0000010F-0000-0000-C000-000000000046")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IAdviseSink { - /// - /// Called by the server to notify a data object's currently registered - /// advise sinks that data in the object has changed. - /// + /// + /// Called by the server to notify a data object's currently registered + /// advise sinks that data in the object has changed. + /// [PreserveSig] void OnDataChange([In] ref FORMATETC format, [In] ref STGMEDIUM stgmedium); - /// - /// Notifies an object's registered advise sinks that its view has changed. - /// + /// + /// Notifies an object's registered advise sinks that its view has changed. + /// [PreserveSig] void OnViewChange(int aspect, int index); - /// - /// Called by the server to notify all registered advisory sinks that - /// the object has been renamed. - /// + /// + /// Called by the server to notify all registered advisory sinks that + /// the object has been renamed. + /// [PreserveSig] void OnRename(IMoniker moniker); - /// - /// Called by the server to notify all registered advisory sinks that - /// the object has been saved. - /// + /// + /// Called by the server to notify all registered advisory sinks that + /// the object has been saved. + /// [PreserveSig] void OnSave(); - /// - /// Called by the server to notify all registered advisory sinks that the - /// object has changed from the running to the loaded state. - /// + /// + /// Called by the server to notify all registered advisory sinks that the + /// object has changed from the running to the loaded state. + /// [PreserveSig] void OnClose(); } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IDataObject.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IDataObject.cs index c41c04876d76..57c8bc9bed38 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IDataObject.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IDataObject.cs @@ -4,7 +4,7 @@ namespace System.Runtime.InteropServices.ComTypes { - /// + /// /// The IDataObject interface specifies methods that enable data transfer /// and notification of changes in data. Data transfer methods specify /// the format of the transferred data along with the medium through @@ -13,75 +13,75 @@ namespace System.Runtime.InteropServices.ComTypes /// retrieving and storing data, the IDataObject interface specifies /// methods for enumerating available formats and managing connections /// to advisory sinks for handling change notifications. - /// + /// [CLSCompliant(false)] [ComImport()] [Guid("0000010E-0000-0000-C000-000000000046")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IDataObject { - /// + /// /// Called by a data consumer to obtain data from a source data object. /// The GetData method renders the data described in the specified FORMATETC /// structure and transfers it through the specified STGMEDIUM structure. /// The caller then assumes responsibility for releasing the STGMEDIUM structure. - /// + /// void GetData([In] ref FORMATETC format, out STGMEDIUM medium); - /// + /// /// Called by a data consumer to obtain data from a source data object. /// This method differs from the GetData method in that the caller must /// allocate and free the specified storage medium. - /// + /// void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium); - /// + /// /// Determines whether the data object is capable of rendering the data /// described in the FORMATETC structure. Objects attempting a paste or /// drop operation can call this method before calling IDataObject::GetData /// to get an indication of whether the operation may be successful. - /// + /// [PreserveSig] int QueryGetData([In] ref FORMATETC format); - /// + /// /// Provides a standard FORMATETC structure that is logically equivalent to one that is more /// complex. You use this method to determine whether two different /// FORMATETC structures would return the same data, removing the need /// for duplicate rendering. - /// + /// [PreserveSig] int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut); - /// + /// /// Called by an object containing a data source to transfer data to /// the object that implements this method. - /// + /// void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release); - /// + /// /// Creates an object for enumerating the FORMATETC structures for a /// data object. These structures are used in calls to IDataObject::GetData /// or IDataObject::SetData. - /// + /// IEnumFORMATETC EnumFormatEtc(DATADIR direction); - /// + /// /// Called by an object supporting an advise sink to create a connection between /// a data object and the advise sink. This enables the advise sink to be /// notified of changes in the data of the object. - /// + /// [PreserveSig] int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection); - /// + /// /// Destroys a notification connection that had been previously set up. - /// + /// void DUnadvise(int connection); - /// + /// /// Creates an object that can be used to enumerate the current advisory connections. - /// + /// [PreserveSig] int EnumDAdvise(out IEnumSTATDATA enumAdvise); } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumFormatETC.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumFormatETC.cs index 3b88b9bc41c8..8b07131bd07b 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumFormatETC.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumFormatETC.cs @@ -4,44 +4,44 @@ namespace System.Runtime.InteropServices.ComTypes { - /// + /// /// The IEnumFORMATETC interface is used to enumerate an array of FORMATETC /// structures. IEnumFORMATETC has the same methods as all enumerator interfaces: /// Next, Skip, Reset, and Clone. - /// + /// [ComImport()] [Guid("00000103-0000-0000-C000-000000000046")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IEnumFORMATETC { - /// + /// /// Retrieves the next celt items in the enumeration sequence. If there are /// fewer than the requested number of elements left in the sequence, it /// retrieves the remaining elements. The number of elements actually /// retrieved is returned through pceltFetched (unless the caller passed /// in NULL for that parameter). - /// + /// [PreserveSig] int Next(int celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] FORMATETC[] rgelt, [Out, MarshalAs(UnmanagedType.LPArray)] int[] pceltFetched); - /// + /// /// Skips over the next specified number of elements in the enumeration sequence. - /// + /// [PreserveSig] int Skip(int celt); - /// + /// /// Resets the enumeration sequence to the beginning. - /// + /// [PreserveSig] int Reset(); - /// + /// /// Creates another enumerator that contains the same enumeration state as /// the current one. Using this function, a client can record a particular /// point in the enumeration sequence and then return to that point at a /// later time. The new enumerator supports the same interface as the original one. - /// + /// void Clone(out IEnumFORMATETC newEnum); } } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumSTATDATA.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumSTATDATA.cs index 1c05186040ca..50af03a58590 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumSTATDATA.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/IEnumSTATDATA.cs @@ -4,46 +4,46 @@ namespace System.Runtime.InteropServices.ComTypes { - /// - /// The IEnumSTATDATA interface is used to enumerate through an array of - /// STATDATA structures, which contain advisory connection information for - /// a data object. IEnumSTATDATA has the same methods as all enumerator - /// interfaces: Next, Skip, Reset, and Clone. - /// + /// + /// The IEnumSTATDATA interface is used to enumerate through an array of + /// STATDATA structures, which contain advisory connection information for + /// a data object. IEnumSTATDATA has the same methods as all enumerator + /// interfaces: Next, Skip, Reset, and Clone. + /// [ComImport()] [Guid("00000103-0000-0000-C000-000000000046")] [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] public interface IEnumSTATDATA { - /// - /// Retrieves the next celt items in the enumeration sequence. If there are - /// fewer than the requested number of elements left in the sequence, it - /// retrieves the remaining elements. The number of elements actually - /// retrieved is returned through pceltFetched (unless the caller passed - /// in NULL for that parameter). - /// + /// + /// Retrieves the next celt items in the enumeration sequence. If there are + /// fewer than the requested number of elements left in the sequence, it + /// retrieves the remaining elements. The number of elements actually + /// retrieved is returned through pceltFetched (unless the caller passed + /// in NULL for that parameter). + /// [PreserveSig] int Next(int celt, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] STATDATA[] rgelt, [Out, MarshalAs(UnmanagedType.LPArray, SizeConst=1)] int[] pceltFetched); - /// - /// Skips over the next specified number of elements in the enumeration sequence. - /// + /// + /// Skips over the next specified number of elements in the enumeration sequence. + /// [PreserveSig] int Skip(int celt); - /// - /// Resets the enumeration sequence to the beginning. - /// + /// + /// Resets the enumeration sequence to the beginning. + /// [PreserveSig] int Reset(); - /// - /// Creates another enumerator that contains the same enumeration state as - /// the current one. Using this function, a client can record a particular - /// point in the enumeration sequence and then return to that point at a - /// later time. The new enumerator supports the same interface as the original one. - /// + /// + /// Creates another enumerator that contains the same enumeration state as + /// the current one. Using this function, a client can record a particular + /// point in the enumeration sequence and then return to that point at a + /// later time. The new enumerator supports the same interface as the original one. + /// void Clone(out IEnumSTATDATA newEnum); } } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/statdata.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/STATDATA.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/statdata.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/STATDATA.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/STGMEDIUM.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/stgmedium.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/STGMEDIUM.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/tymed.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/TYMED.cs similarity index 100% rename from src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/tymed.cs rename to src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComTypes/TYMED.cs diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComUnregisterFunctionAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComUnregisterFunctionAttribute.cs new file mode 100644 index 000000000000..079d1e3ce3eb --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ComUnregisterFunctionAttribute.cs @@ -0,0 +1,11 @@ +// 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. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class ComUnregisterFunctionAttribute : Attribute + { + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/HandleCollector.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/HandleCollector.cs index 4830c37d2215..879843c0e033 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/HandleCollector.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/HandleCollector.cs @@ -8,13 +8,13 @@ namespace System.Runtime.InteropServices { public sealed class HandleCollector { - private const int DeltaPercent = 10; // this is used for increasing the threshold. - private int _initialThreshold; + // Used for increasing the threshold. + private const int DeltaPercent = 10; private int _threshold; private int _handleCount; - private int[] _gc_counts = new int[3]; - private int _gc_gen = 0; + private int[] _gcCounts = new int[3]; + private int _gcGeneration = 0; public HandleCollector(string name, int initialThreshold) : this(name, initialThreshold, int.MaxValue) @@ -25,21 +25,19 @@ public HandleCollector(string name, int initialThreshold, int maximumThreshold) { if (initialThreshold < 0) { - throw new ArgumentOutOfRangeException(nameof(initialThreshold), SR.Arg_NeedNonNegNumRequired); + throw new ArgumentOutOfRangeException(nameof(initialThreshold), initialThreshold, SR.Arg_NeedNonNegNumRequired); } - if (maximumThreshold < 0) { - throw new ArgumentOutOfRangeException(nameof(maximumThreshold), SR.Arg_NeedNonNegNumRequired); + throw new ArgumentOutOfRangeException(nameof(maximumThreshold), maximumThreshold, SR.Arg_NeedNonNegNumRequired); } - if (initialThreshold > maximumThreshold) { - throw new ArgumentException(SR.Arg_InvalidThreshold); + throw new ArgumentException(SR.Arg_InvalidThreshold, nameof(initialThreshold)); } Name = name ?? string.Empty; - _initialThreshold = initialThreshold; + InitialThreshold = initialThreshold; MaximumThreshold = maximumThreshold; _threshold = initialThreshold; _handleCount = 0; @@ -47,7 +45,7 @@ public HandleCollector(string name, int initialThreshold, int maximumThreshold) public int Count => _handleCount; - public int InitialThreshold => _initialThreshold; + public int InitialThreshold { get; } public int MaximumThreshold { get; } @@ -55,7 +53,7 @@ public HandleCollector(string name, int initialThreshold, int maximumThreshold) public void Add() { - int gen_collect = -1; + int collectionGeneration = -1; Interlocked.Increment(ref _handleCount); if (_handleCount < 0) { @@ -67,26 +65,26 @@ public void Add() lock (this) { _threshold = _handleCount + (_handleCount / DeltaPercent); - gen_collect = _gc_gen; - if (_gc_gen < 2) + collectionGeneration = _gcGeneration; + if (_gcGeneration < 2) { - _gc_gen++; + _gcGeneration++; } } } - if ((gen_collect >= 0) && - ((gen_collect == 0) || - (_gc_counts[gen_collect] == GC.CollectionCount(gen_collect)))) + if ((collectionGeneration >= 0) && + ((collectionGeneration == 0) || + (_gcCounts[collectionGeneration] == GC.CollectionCount(collectionGeneration)))) { - GC.Collect(gen_collect); - Thread.Sleep(10 * gen_collect); + GC.Collect(collectionGeneration); + Thread.Sleep(10 * collectionGeneration); } - //don't bother with gen0. + // Don't bother with gen0. for (int i = 1; i < 3; i++) { - _gc_counts[i] = GC.CollectionCount(i); + _gcCounts[i] = GC.CollectionCount(i); } } @@ -103,21 +101,21 @@ public void Remove() { lock (this) { - if (newThreshold > _initialThreshold) + if (newThreshold > InitialThreshold) { _threshold = newThreshold; } else { - _threshold = _initialThreshold; + _threshold = InitialThreshold; } - _gc_gen = 0; + _gcGeneration = 0; } } for (int i = 1; i < 3; i++) { - _gc_counts[i] = GC.CollectionCount(i); + _gcCounts[i] = GC.CollectionCount(i); } } } diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplAttribute.cs new file mode 100644 index 000000000000..1e74dc64033d --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Assembly, Inherited = false)] + [Obsolete("This attribute is deprecated and will be removed in a future version.", error: false)] + public sealed class IDispatchImplAttribute : Attribute + { + public IDispatchImplAttribute(short implType) : this((IDispatchImplType)implType) + { + } + + public IDispatchImplAttribute(IDispatchImplType implType) => Value = implType; + + public IDispatchImplType Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplType.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplType.cs new file mode 100644 index 000000000000..6444a61cb190 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IDispatchImplType.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [Obsolete("The IDispatchImplAttribute is deprecated.", error: false)] + public enum IDispatchImplType + { + CompatibleImpl = 2, + InternalImpl = 1, + SystemDefinedImpl = 0, + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IMarshal.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IMarshal.cs index 5e2611381a70..cd5ea783df31 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IMarshal.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/IMarshal.cs @@ -4,8 +4,9 @@ namespace System.Runtime.InteropServices { - [ComImport, Guid("00000003-0000-0000-C000-000000000046"), - InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + [Guid("00000003-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IMarshal { [PreserveSig] diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ImportedFromTypeLibAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ImportedFromTypeLibAttribute.cs new file mode 100644 index 000000000000..af128f0a5736 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ImportedFromTypeLibAttribute.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class ImportedFromTypeLibAttribute : Attribute + { + public ImportedFromTypeLibAttribute(string tlbFile) => Value = tlbFile; + + public string Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ManagedToNativeComInteropStubAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ManagedToNativeComInteropStubAttribute.cs new file mode 100644 index 000000000000..a8a4736503fc --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/ManagedToNativeComInteropStubAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)] + public sealed class ManagedToNativeComInteropStubAttribute : Attribute + { + public ManagedToNativeComInteropStubAttribute(Type classType, string methodName) + { + ClassType = classType; + MethodName = methodName; + } + + public Type ClassType { get; } + public string MethodName { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PrimaryInteropAssemblyAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PrimaryInteropAssemblyAttribute.cs new file mode 100644 index 000000000000..835dc37949b5 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/PrimaryInteropAssemblyAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] + public sealed class PrimaryInteropAssemblyAttribute : Attribute + { + public PrimaryInteropAssemblyAttribute(int major, int minor) + { + MajorVersion = major; + MinorVersion = minor; + } + + public int MajorVersion { get; } + public int MinorVersion { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/SetWin32ContextInIDispatchAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/SetWin32ContextInIDispatchAttribute.cs new file mode 100644 index 000000000000..609a8d139e51 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/SetWin32ContextInIDispatchAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [Obsolete("This attribute has been deprecated. Application Domains no longer respect Activation Context boundaries in IDispatch calls.", error: false)] + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class SetWin32ContextInIDispatchAttribute : Attribute + { + public SetWin32ContextInIDispatchAttribute() + { + } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/StandardOleMarshalObject.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/StandardOleMarshalObject.cs index b659347faa23..d7c3009716ca 100644 --- a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/StandardOleMarshalObject.cs +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/StandardOleMarshalObject.cs @@ -11,23 +11,25 @@ public class StandardOleMarshalObject : MarshalByRefObject, IMarshal private static readonly Guid CLSID_StdMarshal = new Guid("00000017-0000-0000-c000-000000000046"); [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate int GetMarshalSizeMax_Delegate(IntPtr _this, ref Guid riid, IntPtr pv, int dwDestContext, IntPtr pvDestContext, int mshlflags, out int pSize); + private delegate int GetMarshalSizeMaxDelegate(IntPtr _this, ref Guid riid, IntPtr pv, int dwDestContext, IntPtr pvDestContext, int mshlflags, out int pSize); [UnmanagedFunctionPointer(CallingConvention.StdCall)] - private delegate int MarshalInterface_Delegate(IntPtr _this, IntPtr pStm, ref Guid riid, IntPtr pv, int dwDestContext, IntPtr pvDestContext, int mshlflags); + private delegate int MarshalInterfaceDelegate(IntPtr _this, IntPtr pStm, ref Guid riid, IntPtr pv, int dwDestContext, IntPtr pvDestContext, int mshlflags); - protected StandardOleMarshalObject() { } + protected StandardOleMarshalObject() + { + } private IntPtr GetStdMarshaler(ref Guid riid, int dwDestContext, int mshlflags) { - IntPtr pStandardMarshal = IntPtr.Zero; - IntPtr pUnknown = Marshal.GetIUnknownForObject(this); if (pUnknown != IntPtr.Zero) { try { - if (HResults.S_OK == Interop.Ole32.CoGetStandardMarshal(ref riid, pUnknown, dwDestContext, IntPtr.Zero, mshlflags, out pStandardMarshal)) + IntPtr pStandardMarshal = IntPtr.Zero; + int hr = Interop.Ole32.CoGetStandardMarshal(ref riid, pUnknown, dwDestContext, IntPtr.Zero, mshlflags, out pStandardMarshal); + if (hr == HResults.S_OK) { Debug.Assert(pStandardMarshal != IntPtr.Zero, "Failed to get marshaler for interface '" + riid.ToString() + "', CoGetStandardMarshal returned S_OK"); return pStandardMarshal; @@ -38,7 +40,8 @@ private IntPtr GetStdMarshaler(ref Guid riid, int dwDestContext, int mshlflags) Marshal.Release(pUnknown); } } - throw new InvalidOperationException(SR.Format(SR.StandardOleMarshalObjectGetMarshalerFailed, riid.ToString())); + + throw new InvalidOperationException(SR.Format(SR.StandardOleMarshalObjectGetMarshalerFailed, riid)); } int IMarshal.GetUnmarshalClass(ref Guid riid, IntPtr pv, int dwDestContext, IntPtr pvDestContext, int mshlflags, out Guid pCid) @@ -53,12 +56,16 @@ unsafe int IMarshal.GetMarshalSizeMax(ref Guid riid, IntPtr pv, int dwDestContex try { - // we must not wrap pStandardMarshal with an RCW because that would trigger QIs for random IIDs and the marshaler - // (aka stub manager object) does not really handle these well and we would risk triggering an AppVerifier break + // We must not wrap pStandardMarshal with an RCW because that + // would trigger QIs for random IIDs and the marshaler (aka stub + // manager object) does not really handle these well and we would + // risk triggering an AppVerifier break IntPtr vtable = *(IntPtr*)pStandardMarshal.ToPointer(); - IntPtr method = *((IntPtr*)vtable.ToPointer() + 4); // GetMarshalSizeMax is 4th slot + + // GetMarshalSizeMax is 4th slot + IntPtr method = *((IntPtr*)vtable.ToPointer() + 4); - GetMarshalSizeMax_Delegate del = (GetMarshalSizeMax_Delegate)Marshal.GetDelegateForFunctionPointer(method, typeof(GetMarshalSizeMax_Delegate)); + GetMarshalSizeMaxDelegate del = (GetMarshalSizeMaxDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(GetMarshalSizeMaxDelegate)); return del(pStandardMarshal, ref riid, pv, dwDestContext, pvDestContext, mshlflags, out pSize); } finally @@ -73,12 +80,14 @@ unsafe int IMarshal.MarshalInterface(IntPtr pStm, ref Guid riid, IntPtr pv, int try { - // we must not wrap pStandardMarshal with an RCW because that would trigger QIs for random IIDs and the marshaler - // (aka stub manager object) does not really handle these well and we would risk triggering an AppVerifier break + // We must not wrap pStandardMarshal with an RCW because that + // would trigger QIs for random IIDs and the marshaler (aka stub + // manager object) does not really handle these well and we would + // risk triggering an AppVerifier break IntPtr vtable = *(IntPtr*)pStandardMarshal.ToPointer(); IntPtr method = *((IntPtr*)vtable.ToPointer() + 5); // MarshalInterface is 5th slot - MarshalInterface_Delegate del = (MarshalInterface_Delegate)Marshal.GetDelegateForFunctionPointer(method, typeof(MarshalInterface_Delegate)); + MarshalInterfaceDelegate del = (MarshalInterfaceDelegate)Marshal.GetDelegateForFunctionPointer(method, typeof(MarshalInterfaceDelegate)); return del(pStandardMarshal, pStm, ref riid, pv, dwDestContext, pvDestContext, mshlflags); } finally diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncAttribute.cs new file mode 100644 index 000000000000..aae81a440b77 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + public sealed class TypeLibFuncAttribute : Attribute + { + public TypeLibFuncAttribute(TypeLibFuncFlags flags) + { + Value = flags; + } + + public TypeLibFuncAttribute(short flags) + { + Value = (TypeLibFuncFlags)flags; + } + + public TypeLibFuncFlags Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncFlags.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncFlags.cs new file mode 100644 index 000000000000..8a04d89e7a45 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibFuncFlags.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [Flags] + public enum TypeLibFuncFlags + { + FRestricted = 0x0001, + FSource = 0x0002, + FBindable = 0x0004, + FRequestEdit = 0x0008, + FDisplayBind = 0x0010, + FDefaultBind = 0x0020, + FHidden = 0x0040, + FUsesGetLastError = 0x0080, + FDefaultCollelem = 0x0100, + FUiDefault = 0x0200, + FNonBrowsable = 0x0400, + FReplaceable = 0x0800, + FImmediateBind = 0x1000, + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibImportClassAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibImportClassAttribute.cs new file mode 100644 index 000000000000..6ff1d6ee43d8 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibImportClassAttribute.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Interface, Inherited = false)] + public sealed class TypeLibImportClassAttribute : Attribute + { + public TypeLibImportClassAttribute(Type importClass) => Value = importClass.ToString(); + + public string Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeAttribute.cs new file mode 100644 index 000000000000..90d18e759578 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Struct, Inherited = false)] + public sealed class TypeLibTypeAttribute : Attribute + { + public TypeLibTypeAttribute(TypeLibTypeFlags flags) + { + Value = flags; + } + + public TypeLibTypeAttribute(short flags) + { + Value = (TypeLibTypeFlags)flags; + } + + public TypeLibTypeFlags Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeFlags.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeFlags.cs new file mode 100644 index 000000000000..d5d719592468 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibTypeFlags.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [Flags] + public enum TypeLibTypeFlags + { + FAppObject = 0x0001, + FCanCreate = 0x0002, + FLicensed = 0x0004, + FPreDeclId = 0x0008, + FHidden = 0x0010, + FControl = 0x0020, + FDual = 0x0040, + FNonExtensible = 0x0080, + FOleAutomation = 0x0100, + FRestricted = 0x0200, + FAggregatable = 0x0400, + FReplaceable = 0x0800, + FDispatchable = 0x1000, + FReverseBind = 0x2000, + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarAttribute.cs new file mode 100644 index 000000000000..5961b129442a --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Field, Inherited = false)] + public sealed class TypeLibVarAttribute : Attribute + { + public TypeLibVarAttribute(TypeLibVarFlags flags) + { + Value = flags; + } + + public TypeLibVarAttribute(short flags) + { + Value = (TypeLibVarFlags)flags; + } + + public TypeLibVarFlags Value { get; } + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarFlags.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarFlags.cs new file mode 100644 index 000000000000..b3b37726162f --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVarFlags.cs @@ -0,0 +1,25 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [Flags] + public enum TypeLibVarFlags + { + FReadOnly = 0x0001, + FSource = 0x0002, + FBindable = 0x0004, + FRequestEdit = 0x0008, + FDisplayBind = 0x0010, + FDefaultBind = 0x0020, + FHidden = 0x0040, + FRestricted = 0x0080, + FDefaultCollelem = 0x0100, + FUiDefault = 0x0200, + FNonBrowsable = 0x0400, + FReplaceable = 0x0800, + FImmediateBind = 0x1000, + } +} diff --git a/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVersionAttribute.cs b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVersionAttribute.cs new file mode 100644 index 000000000000..73ad3c69bf42 --- /dev/null +++ b/src/System.Runtime.InteropServices/src/System/Runtime/InteropServices/TypeLibVersionAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.InteropServices +{ + [AttributeUsage(AttributeTargets.Assembly, Inherited = false)] + public sealed class TypeLibVersionAttribute : Attribute + { + public TypeLibVersionAttribute(int major, int minor) + { + MajorVersion = major; + MinorVersion = minor; + } + + public int MajorVersion { get; } + public int MinorVersion { get; } + } +} diff --git a/src/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj b/src/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj index 3b6be58a75d9..0de8c8b83637 100644 --- a/src/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj +++ b/src/System.Runtime.InteropServices/tests/System.Runtime.InteropServices.Tests.csproj @@ -98,6 +98,7 @@ + @@ -175,4 +176,4 @@ - \ No newline at end of file + diff --git a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/HandleCollectorTests.cs b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/HandleCollectorTests.cs index d67005548d54..8ff50ccfee81 100644 --- a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/HandleCollectorTests.cs +++ b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/HandleCollectorTests.cs @@ -54,7 +54,7 @@ public static void Ctor_NegativeMaximumThreshold_ThrowsArgumentOutOfRangeExcepti [Fact] public static void Ctor_InitialThresholdGreaterThanMaximumThreshold_ThrowsArgumentException() { - AssertExtensions.Throws(null, () => new HandleCollector("InitialGreaterThanMax", 100, 1)); + AssertExtensions.Throws("initialThreshold", null, () => new HandleCollector("InitialGreaterThanMax", 100, 1)); } [Fact] diff --git a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAnsiTests.cs b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAnsiTests.cs index a5aedaf59ac9..87164eed1957 100644 --- a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAnsiTests.cs +++ b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAnsiTests.cs @@ -49,9 +49,9 @@ public void PtrToStringAnsi_ZeroPointer_ThrowsArgumentNullException() } [Fact] - public void PtrToStringAnsi_NegativeLength_ThrowsArgumentExeption() + public void PtrToStringAnsi_NegativeLength_ThrowsArgumentOutOfRangeExeption() { - AssertExtensions.Throws("len", null, () => Marshal.PtrToStringAnsi(new IntPtr(123), -77)); + AssertExtensions.Throws("len", null, () => Marshal.PtrToStringAnsi(new IntPtr(123), -77)); } } } diff --git a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAutoTests.cs b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAutoTests.cs index 4243105018f7..df15e8361e7c 100644 --- a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAutoTests.cs +++ b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringAutoTests.cs @@ -21,13 +21,13 @@ public void PtrToStringAuto_ZeroPtrWithLength_ThrowsArgumentNullException() } [Fact] - public void PtrToStringAuto_NegativeLength_ThrowsArgumentException() + public void PtrToStringAuto_NegativeLength_ThrowsArgumentOutOfRangeException() { string s = "Hello World"; IntPtr ptr = Marshal.StringToCoTaskMemAuto(s); try { - AssertExtensions.Throws("len", null, () => Marshal.PtrToStringAuto(ptr, -1)); + AssertExtensions.Throws(() => Marshal.PtrToStringAuto(ptr, -1)); } finally { diff --git a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUTF8Tests.cs b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUTF8Tests.cs index bf72a552199f..6b2461a154cb 100644 --- a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUTF8Tests.cs +++ b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUTF8Tests.cs @@ -31,8 +31,6 @@ public void PtrToStringUTF8_Length_Success(string s, int len) public void PtrToStringUTF8_ZeroPointer_ReturnsNull() { Assert.Null(Marshal.PtrToStringUTF8(IntPtr.Zero)); - Assert.Null(Marshal.PtrToStringUTF8(IntPtr.Zero, 0)); - Assert.Null(Marshal.PtrToStringUTF8(IntPtr.Zero, 1)); } [Fact] @@ -41,14 +39,21 @@ public void PtrToStringUTF8_Win32AtomPointer_ReturnsNull() { // Windows Marshal has specific checks that does not do // anything if the ptr is less than 64K. - Assert.Null(Marshal.PtrToStringUTF8((IntPtr)1, 10)); + Assert.Null(Marshal.PtrToStringUTF8((IntPtr)1)); } [Fact] - public void PtrToStringUTF8_NegativeLength_ThrowsArgumentOutOfRangeExeption() + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PtrToStringUTF8_ZeroPointer_ThrowsArgumentNullException() { - AssertExtensions.Throws("byteLen", null, () => Marshal.PtrToStringUTF8(new IntPtr(123), -77)); - AssertExtensions.Throws("byteLen", null, () => Marshal.PtrToStringUTF8(IntPtr.Zero, -77)); + AssertExtensions.Throws("ptr", () => Marshal.PtrToStringUTF8(IntPtr.Zero, 123)); + } + + [Fact] + [SkipOnTargetFramework(~TargetFrameworkMonikers.NetFramework)] + public void PtrToStringUTF8_NegativeLength_ThrowsArgumentExeption() + { + AssertExtensions.Throws("byteLen", null, () => Marshal.PtrToStringUTF8(new IntPtr(123), -77)); } } } diff --git a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUniTests.cs b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUniTests.cs index 4d4be3db5e2a..6c2ef1edd85f 100644 --- a/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUniTests.cs +++ b/src/System.Runtime.InteropServices/tests/System/Runtime/InteropServices/Marshal/PtrToStringUniTests.cs @@ -49,9 +49,9 @@ public void PtrToStringUni_ZeroPointer_ThrowsArgumentNullException() } [Fact] - public void PtrToStringUni_NegativeLength_ThrowsArgumentExeption() + public void PtrToStringUni_NegativeLength_ThrowsArgumentOutOfRangeExeption() { - AssertExtensions.Throws("len", null, () => Marshal.PtrToStringUni(new IntPtr(123), -77)); + AssertExtensions.Throws("len", null, () => Marshal.PtrToStringUni(new IntPtr(123), -77)); } } } diff --git a/src/System.Runtime.Intrinsics.Experimental/src/Configurations.props b/src/System.Runtime.Intrinsics.Experimental/src/Configurations.props index 002c6d3b9d61..adabcc0290e8 100644 --- a/src/System.Runtime.Intrinsics.Experimental/src/Configurations.props +++ b/src/System.Runtime.Intrinsics.Experimental/src/Configurations.props @@ -2,9 +2,7 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; netcoreapp-Unix; - netcoreappaot-WebAssembly; diff --git a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs b/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs index bfab8d8ae42f..8a39d759df2e 100644 --- a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs +++ b/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.cs @@ -592,18 +592,30 @@ internal Avx2() { } public static System.Runtime.Intrinsics.Vector256 CompareGreaterThan(System.Runtime.Intrinsics.Vector256 left, System.Runtime.Intrinsics.Vector256 right) { throw null; } public static int ConvertToInt32(System.Runtime.Intrinsics.Vector256 value) { throw null; } public static uint ConvertToUInt32(System.Runtime.Intrinsics.Vector256 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int16(byte* address) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int16(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int16(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int16(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(byte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(short* address) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int32(ushort* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(byte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(short* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(int* address) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt16(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt32(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt32(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt64(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt64(System.Runtime.Intrinsics.Vector128 value) { throw null; } - public static System.Runtime.Intrinsics.Vector256 ConvertToVector256UInt64(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(ushort* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector256 ConvertToVector256Int64(uint* address) { throw null; } public static new System.Runtime.Intrinsics.Vector128 ExtractVector128(System.Runtime.Intrinsics.Vector256 value, byte index) { throw null; } public static new System.Runtime.Intrinsics.Vector128 ExtractVector128(System.Runtime.Intrinsics.Vector256 value, byte index) { throw null; } public static new System.Runtime.Intrinsics.Vector128 ExtractVector128(System.Runtime.Intrinsics.Vector256 value, byte index) { throw null; } @@ -907,38 +919,38 @@ internal X64() { } } public enum FloatComparisonMode : byte { - EqualOrderedNonSignaling = (byte)0, - EqualOrderedSignaling = (byte)16, - EqualUnorderedNonSignaling = (byte)8, - EqualUnorderedSignaling = (byte)24, - FalseOrderedNonSignaling = (byte)11, - FalseOrderedSignaling = (byte)27, - GreaterThanOrderedNonSignaling = (byte)30, - GreaterThanOrderedSignaling = (byte)14, - GreaterThanOrEqualOrderedNonSignaling = (byte)29, - GreaterThanOrEqualOrderedSignaling = (byte)13, - LessThanOrderedNonSignaling = (byte)17, - LessThanOrderedSignaling = (byte)1, - LessThanOrEqualOrderedNonSignaling = (byte)18, - LessThanOrEqualOrderedSignaling = (byte)2, - NotEqualOrderedNonSignaling = (byte)12, - NotEqualOrderedSignaling = (byte)28, - NotEqualUnorderedNonSignaling = (byte)4, - NotEqualUnorderedSignaling = (byte)20, - NotGreaterThanOrEqualUnorderedNonSignaling = (byte)25, - NotGreaterThanOrEqualUnorderedSignaling = (byte)9, - NotGreaterThanUnorderedNonSignaling = (byte)26, - NotGreaterThanUnorderedSignaling = (byte)10, - NotLessThanOrEqualUnorderedNonSignaling = (byte)22, - NotLessThanOrEqualUnorderedSignaling = (byte)6, - NotLessThanUnorderedNonSignaling = (byte)21, - NotLessThanUnorderedSignaling = (byte)5, + OrderedEqualNonSignaling = (byte)0, + OrderedEqualSignaling = (byte)16, + OrderedFalseNonSignaling = (byte)11, + OrderedFalseSignaling = (byte)27, + OrderedGreaterThanNonSignaling = (byte)30, + OrderedGreaterThanOrEqualNonSignaling = (byte)29, + OrderedGreaterThanOrEqualSignaling = (byte)13, + OrderedGreaterThanSignaling = (byte)14, + OrderedLessThanNonSignaling = (byte)17, + OrderedLessThanOrEqualNonSignaling = (byte)18, + OrderedLessThanOrEqualSignaling = (byte)2, + OrderedLessThanSignaling = (byte)1, OrderedNonSignaling = (byte)7, + OrderedNotEqualNonSignaling = (byte)12, + OrderedNotEqualSignaling = (byte)28, OrderedSignaling = (byte)23, - TrueUnorderedNonSignaling = (byte)15, - TrueUnorderedSignaling = (byte)31, + UnorderedEqualNonSignaling = (byte)8, + UnorderedEqualSignaling = (byte)24, UnorderedNonSignaling = (byte)3, + UnorderedNotEqualNonSignaling = (byte)4, + UnorderedNotEqualSignaling = (byte)20, + UnorderedNotGreaterThanNonSignaling = (byte)26, + UnorderedNotGreaterThanOrEqualNonSignaling = (byte)25, + UnorderedNotGreaterThanOrEqualSignaling = (byte)9, + UnorderedNotGreaterThanSignaling = (byte)10, + UnorderedNotLessThanNonSignaling = (byte)21, + UnorderedNotLessThanOrEqualNonSignaling = (byte)22, + UnorderedNotLessThanOrEqualSignaling = (byte)6, + UnorderedNotLessThanSignaling = (byte)5, UnorderedSignaling = (byte)19, + UnorderedTrueNonSignaling = (byte)15, + UnorderedTrueSignaling = (byte)31, } public abstract partial class Fma : System.Runtime.Intrinsics.X86.Avx { @@ -1001,7 +1013,7 @@ public abstract partial class Popcnt : System.Runtime.Intrinsics.X86.Sse42 internal Popcnt() { } public static new bool IsSupported { get { throw null; } } public static uint PopCount(uint value) { throw null; } - public abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse41.X64 + public new abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse41.X64 { internal X64() { } public static new bool IsSupported { get { throw null; } } @@ -1017,41 +1029,41 @@ internal Sse() { } public static System.Runtime.Intrinsics.Vector128 And(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 AndNot(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareLessThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareLessThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareNotEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareNotEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareOrdered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarOrdered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarUnordered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareUnordered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertScalarToVector128Single(System.Runtime.Intrinsics.Vector128 upper, int value) { throw null; } public static int ConvertToInt32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static int ConvertToInt32WithTruncation(System.Runtime.Intrinsics.Vector128 value) { throw null; } @@ -1153,47 +1165,47 @@ internal Sse2() { } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanOrEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareGreaterThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareGreaterThanUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareLessThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanOrEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareLessThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareLessThanUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareNotEqualOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static bool CompareNotEqualUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotGreaterThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanOrEqualScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareNotLessThanScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareOrdered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareOrderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarNotLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarOrdered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarOrderedNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static System.Runtime.Intrinsics.Vector128 CompareScalarUnordered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedGreaterThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedGreaterThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedLessThan(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedLessThanOrEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public static bool CompareScalarUnorderedNotEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareUnordered(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public static System.Runtime.Intrinsics.Vector128 CompareUnorderedScalar(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertScalarToVector128Double(System.Runtime.Intrinsics.Vector128 upper, int value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertScalarToVector128Double(System.Runtime.Intrinsics.Vector128 upper, System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertScalarToVector128Int32(int value) { throw null; } @@ -1409,7 +1421,7 @@ public unsafe static void StoreScalar(double* address, System.Runtime.Intrinsics public static System.Runtime.Intrinsics.Vector128 Xor(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 Xor(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 Xor(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse.X64 + public new abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse.X64 { internal X64() { } public static new bool IsSupported { get { throw null; } } @@ -1473,18 +1485,30 @@ internal Sse41() { } public static System.Runtime.Intrinsics.Vector128 CeilingScalar(System.Runtime.Intrinsics.Vector128 upper, System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static System.Runtime.Intrinsics.Vector128 CompareEqual(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int16(byte* address) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int16(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int16(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int16(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(byte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(short* address) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int32(ushort* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(byte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(short* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(int* address) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } public static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(System.Runtime.Intrinsics.Vector128 value) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(sbyte* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(ushort* address) { throw null; } + public unsafe static System.Runtime.Intrinsics.Vector128 ConvertToVector128Int64(uint* address) { throw null; } public static System.Runtime.Intrinsics.Vector128 DotProduct(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, byte control) { throw null; } public static System.Runtime.Intrinsics.Vector128 DotProduct(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right, byte control) { throw null; } public static byte Extract(System.Runtime.Intrinsics.Vector128 value, byte index) { throw null; } @@ -1602,7 +1626,7 @@ internal Sse41() { } public static bool TestZ(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static bool TestZ(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } public static bool TestZ(System.Runtime.Intrinsics.Vector128 left, System.Runtime.Intrinsics.Vector128 right) { throw null; } - public abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse2.X64 + public new abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse2.X64 { internal X64() { } public static new bool IsSupported { get { throw null; } } @@ -1620,7 +1644,7 @@ internal Sse42() { } public static uint Crc32(uint crc, byte data) { throw null; } public static uint Crc32(uint crc, ushort data) { throw null; } public static uint Crc32(uint crc, uint data) { throw null; } - public abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse41.X64 + public new abstract partial class X64 : System.Runtime.Intrinsics.X86.Sse41.X64 { internal X64() { } public static new bool IsSupported { get { throw null; } } diff --git a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.csproj b/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.csproj index 1e0992569325..b62e54d6c86a 100644 --- a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.csproj +++ b/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.csproj @@ -7,7 +7,6 @@ - diff --git a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.manual.cs b/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.manual.cs deleted file mode 100644 index 63de7da870f3..000000000000 --- a/src/System.Runtime.Intrinsics/ref/System.Runtime.Intrinsics.manual.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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. -// ------------------------------------------------------------------------------ -// Changes to this file must follow the http://aka.ms/api-review process. -// ------------------------------------------------------------------------------ - -[assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.MidpointRounding))] - -namespace System.Runtime.Intrinsics.X86 -{ - public abstract partial class Popcnt - { - public new abstract partial class X64 { } - } - public abstract partial class Sse2 - { - public new abstract partial class X64 { } - } - public abstract partial class Sse41 - { - public new abstract partial class X64 { } - } - public abstract partial class Sse42 - { - public new abstract partial class X64 { } - } -} diff --git a/src/System.Runtime.Intrinsics/src/Configurations.props b/src/System.Runtime.Intrinsics/src/Configurations.props index 002c6d3b9d61..adabcc0290e8 100644 --- a/src/System.Runtime.Intrinsics/src/Configurations.props +++ b/src/System.Runtime.Intrinsics/src/Configurations.props @@ -2,9 +2,7 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; netcoreapp-Unix; - netcoreappaot-WebAssembly; diff --git a/src/System.Runtime.Intrinsics/src/System.Runtime.Intrinsics.csproj b/src/System.Runtime.Intrinsics/src/System.Runtime.Intrinsics.csproj index 4a99e423982f..43d2b0633519 100644 --- a/src/System.Runtime.Intrinsics/src/System.Runtime.Intrinsics.csproj +++ b/src/System.Runtime.Intrinsics/src/System.Runtime.Intrinsics.csproj @@ -6,9 +6,6 @@ {543FBFE5-E9E4-4631-8242-911A707FE818} netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release - - true - diff --git a/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs b/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs index 6709891a50ea..25d551155b5e 100644 --- a/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs +++ b/src/System.Runtime.Loader/ref/System.Runtime.Loader.cs @@ -47,5 +47,13 @@ public event System.Action Unloading public void SetProfileOptimizationRoot(string directoryPath) { } public void StartProfileOptimization(string profile) { } public void Unload() { } + public static AssemblyLoadContext CurrentContextualReflectionContext { get { throw null; } } + public ContextualReflectionScope EnterContextualReflection() { throw null; } + public static ContextualReflectionScope EnterContextualReflection(System.Reflection.Assembly activating) { throw null; } + + public struct ContextualReflectionScope : IDisposable + { + public void Dispose() { } + } } } diff --git a/src/System.Runtime.Loader/src/ApiCompatBaseline.uapaot.txt b/src/System.Runtime.Loader/src/ApiCompatBaseline.uapaot.txt index 6ac3960d6a18..2ed8d5f7f028 100644 --- a/src/System.Runtime.Loader/src/ApiCompatBaseline.uapaot.txt +++ b/src/System.Runtime.Loader/src/ApiCompatBaseline.uapaot.txt @@ -1,6 +1,5 @@ Compat issues with assembly System.Runtime.Loader: CannotMakeTypeAbstract : Type 'System.Runtime.Loader.AssemblyLoadContext' is abstract in the implementation but is not abstract in the contract. -TypesMustExist : Type 'System.Reflection.Metadata.AssemblyExtensions' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext..ctor(System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext..ctor(System.String, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.add_Resolving(System.Func)' does not exist in the implementation but it does exist in the contract. @@ -27,5 +26,10 @@ MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.remove_Unlo MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.SetProfileOptimizationRoot(System.String)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.StartProfileOptimization(System.String)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.Unload()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.CurrentContextualReflectionContext.get()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.EnterContextualReflection()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Runtime.Loader.AssemblyLoadContext.EnterContextualReflection(System.Reflection.Assembly)' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Reflection.Metadata.AssemblyExtensions' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'System.Runtime.Loader.AssemblyDependencyResolver' does not exist in the implementation but it does exist in the contract. -Total Issues: 29 +TypesMustExist : Type 'System.Runtime.Loader.AssemblyLoadContext.ContextualReflectionScope' does not exist in the implementation but it does exist in the contract. +Total Issues: 33 diff --git a/src/System.Runtime.Loader/src/Configurations.props b/src/System.Runtime.Loader/src/Configurations.props index 8b997a7f94c0..63d93e78c952 100644 --- a/src/System.Runtime.Loader/src/Configurations.props +++ b/src/System.Runtime.Loader/src/Configurations.props @@ -2,8 +2,6 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; uapaot-Windows_NT; diff --git a/src/System.Runtime.Loader/src/System.Runtime.Loader.csproj b/src/System.Runtime.Loader/src/System.Runtime.Loader.csproj index 6aab31a150a9..67c31d3617f9 100644 --- a/src/System.Runtime.Loader/src/System.Runtime.Loader.csproj +++ b/src/System.Runtime.Loader/src/System.Runtime.Loader.csproj @@ -4,7 +4,7 @@ true true {485A65F0-51C9-4B95-A7A8-A4860C231E67} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs b/src/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs index 4f43e086fad8..330c0e98af25 100644 --- a/src/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs +++ b/src/System.Runtime.Loader/tests/AssemblyLoadContextTest.cs @@ -145,9 +145,7 @@ public static void DefaultAssemblyLoadContext_Properties() Assert.Contains("\"Default\"", alc.ToString()); Assert.Contains("System.Runtime.Loader.DefaultAssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Contains(Assembly.GetCallingAssembly(), alc.Assemblies); -#endif } [Fact] @@ -161,9 +159,7 @@ public static void PublicConstructor_Default() Assert.Contains("PublicConstructor", alc.ToString()); Assert.Contains("System.Runtime.Loader.AssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Empty(alc.Assemblies); -#endif } [Theory] @@ -179,9 +175,7 @@ public static void PublicConstructor_Theory(string name, bool isCollectible) Assert.Contains(name, alc.ToString()); Assert.Contains("System.Runtime.Loader.AssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Empty(alc.Assemblies); -#endif } } } diff --git a/src/System.Runtime.Loader/tests/DefaultContext/DefaultLoadContextTest.cs b/src/System.Runtime.Loader/tests/DefaultContext/DefaultLoadContextTest.cs index 55f4b709b8dd..13aae6f8a4f4 100644 --- a/src/System.Runtime.Loader/tests/DefaultContext/DefaultLoadContextTest.cs +++ b/src/System.Runtime.Loader/tests/DefaultContext/DefaultLoadContextTest.cs @@ -104,9 +104,7 @@ public void LoadInDefaultContext() // We should have successfully loaded the assembly in secondary load context. Assert.NotNull(slcLoadedAssembly); -#if CoreCLR_23583 Assert.Contains(slcLoadedAssembly, slc.Assemblies); -#endif // And make sure the simple name matches Assert.Equal(TestAssemblyName, slcLoadedAssembly.GetName().Name); @@ -129,9 +127,7 @@ public void LoadInDefaultContext() // We should have successfully loaded the assembly in default context. Assert.NotNull(assemblyExpectedFromLoad); -#if CoreCLR_23583 Assert.Contains(assemblyExpectedFromLoad, AssemblyLoadContext.Default.Assemblies); -#endif // We should have only invoked non-Null returning handler once Assert.Equal(1, _numNonNullResolutions); diff --git a/src/System.Runtime.Serialization.Formatters/src/Configurations.props b/src/System.Runtime.Serialization.Formatters/src/Configurations.props index cdeb74f110c2..ed33972c57a6 100644 --- a/src/System.Runtime.Serialization.Formatters/src/Configurations.props +++ b/src/System.Runtime.Serialization.Formatters/src/Configurations.props @@ -1,8 +1,11 @@  + netcoreapp-Windows_NT; + netcoreapp-Unix; uap-Windows_NT; - netcoreapp; + uapaot-Windows_NT; + $(PackageConfigurations); - \ No newline at end of file + diff --git a/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj b/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj index a27d2690bc27..b0a43f92b5ea 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj +++ b/src/System.Runtime.Serialization.Formatters/src/System.Runtime.Serialization.Formatters.csproj @@ -1,10 +1,10 @@ - + {CF80D24A-787A-43DB-B9E7-10BCA02D10EA} System.Runtime.Serialization.Formatters System.Runtime.Serialization.Formatters $(NoWarn);CS1573 - netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release @@ -60,19 +60,14 @@ - - - - - - - - - - - + + + + + + - \ No newline at end of file + diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs index 2ebf104809ab..9e140b83d36e 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/Formatters/Binary/BinaryObjectReader.cs @@ -87,45 +87,47 @@ internal object Deserialize(BinaryParser serParser, bool fCheck) _isSimpleAssembly = (_formatterEnums._assemblyFormat == FormatterAssemblyStyle.Simple); - - if (_fullDeserialization) + using (DeserializationToken token = SerializationInfo.StartDeserialization()) { - // Reinitialize - _objectManager = new ObjectManager(_surrogates, _context, false, false); - _serObjectInfoInit = new SerObjectInfoInit(); - } + if (_fullDeserialization) + { + // Reinitialize + _objectManager = new ObjectManager(_surrogates, _context, false, false); + _serObjectInfoInit = new SerObjectInfoInit(); + } - // Will call back to ParseObject, ParseHeader for each object found - serParser.Run(); + // Will call back to ParseObject, ParseHeader for each object found + serParser.Run(); - if (_fullDeserialization) - { - _objectManager.DoFixups(); - } + if (_fullDeserialization) + { + _objectManager.DoFixups(); + } - if (TopObject == null) - { - throw new SerializationException(SR.Serialization_TopObject); - } + if (TopObject == null) + { + throw new SerializationException(SR.Serialization_TopObject); + } - //if TopObject has a surrogate then the actual object may be changed during special fixup - //So refresh it using topID. - if (HasSurrogate(TopObject.GetType()) && _topId != 0)//Not yet resolved - { - TopObject = _objectManager.GetObject(_topId); - } + //if TopObject has a surrogate then the actual object may be changed during special fixup + //So refresh it using topID. + if (HasSurrogate(TopObject.GetType()) && _topId != 0)//Not yet resolved + { + TopObject = _objectManager.GetObject(_topId); + } - if (TopObject is IObjectReference) - { - TopObject = ((IObjectReference)TopObject).GetRealObject(_context); - } + if (TopObject is IObjectReference) + { + TopObject = ((IObjectReference)TopObject).GetRealObject(_context); + } - if (_fullDeserialization) - { - _objectManager.RaiseDeserializationEvent(); // This will raise both IDeserialization and [OnDeserialized] events - } + if (_fullDeserialization) + { + _objectManager.RaiseDeserializationEvent(); // This will raise both IDeserialization and [OnDeserialized] events + } - return TopObject; + return TopObject; + } } private bool HasSurrogate(Type t) { diff --git a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs index 3bf0b8e8c7da..39b4e620cfec 100644 --- a/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs +++ b/src/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/ObjectManager.cs @@ -773,8 +773,17 @@ internal void CompleteISerializableObject(object obj, SerializationInfo info, St { throw new SerializationException(SR.Format(SR.Serialization_ConstructorNotFound, t), e); } + try + { + constInfo.Invoke(obj, new object[] { info, context }); + } + // This will only throw TargetInvocationExceptions, but to provide a better exception for dangerous deserialization, unwrap that + // and re-wrap it in a DeserializationBlockedException (while preserving its stack) + catch (TargetInvocationException outerException) when (outerException.InnerException is DeserializationBlockedException) + { + throw new DeserializationBlockedException(outerException.InnerException); + } - constInfo.Invoke(obj, new object[] { info, context }); } internal static ConstructorInfo GetDeserializationConstructor(Type t) diff --git a/src/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs b/src/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs new file mode 100644 index 000000000000..440acc9b0b9d --- /dev/null +++ b/src/System.Runtime.Serialization.Formatters/tests/SerializationGuardTests.cs @@ -0,0 +1,154 @@ +// 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.IO; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.Runtime.Serialization.Formatters.Tests +{ + public static class SerializationGuardTests + { + [Fact] + public static void BlockAssemblyLoads() + { + TryPayload(new AssemblyLoader()); + } + + [Fact] + public static void BlockProcessStarts() + { + TryPayload(new ProcessStarter()); + } + + [Fact] + public static void BlockFileWrites() + { + TryPayload(new FileWriter()); + } + + [Fact] + public static void BlockReflectionDodging() + { + // Ensure that the deserialization tracker cannot be called by reflection. + MethodInfo trackerMethod = typeof(Thread).GetMethod( + "GetThreadDeserializationTracker", + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + + Assert.NotNull(trackerMethod); + + Assert.Equal(1, trackerMethod.GetParameters().Length); + object[] args = new object[1]; + args[0] = Enum.ToObject(typeof(Thread).Assembly.GetType("System.Threading.StackCrawlMark"), 0); + + try + { + object tracker = trackerMethod.Invoke(null, args); + throw new InvalidOperationException(tracker?.ToString() ?? "(null tracker returned)"); + } + catch (TargetInvocationException ex) + { + Exception baseEx = ex.GetBaseException(); + AssertExtensions.Throws("stackMark", () => throw baseEx); + } + } + + [Fact] + public static void BlockAsyncDodging() + { + TryPayload(new AsyncDodger()); + } + + private static void TryPayload(object payload) + { + MemoryStream ms = new MemoryStream(); + BinaryFormatter writer = new BinaryFormatter(); + writer.Serialize(ms, payload); + ms.Position = 0; + + BinaryFormatter reader = new BinaryFormatter(); + Assert.Throws(() => reader.Deserialize(ms)); + } + } + + [Serializable] + internal class AssemblyLoader : ISerializable + { + public AssemblyLoader() { } + + public AssemblyLoader(SerializationInfo info, StreamingContext context) + { + Assembly.Load(new byte[1000]); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + } + } + + [Serializable] + internal class ProcessStarter : ISerializable + { + public ProcessStarter() { } + + private ProcessStarter(SerializationInfo info, StreamingContext context) + { + Process.Start("calc.exe"); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + } + } + + [Serializable] + internal class FileWriter : ISerializable + { + public FileWriter() { } + + private FileWriter(SerializationInfo info, StreamingContext context) + { + string tempPath = Path.GetTempFileName(); + File.WriteAllText(tempPath, "This better not be written..."); + throw new InvalidOperationException("Unreachable code (SerializationGuard should have kicked in)"); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + } + } + + [Serializable] + internal class AsyncDodger : ISerializable + { + public AsyncDodger() { } + + private AsyncDodger(SerializationInfo info, StreamingContext context) + { + try + { + Task t = Task.Factory.StartNew(LoadAssemblyOnBackgroundThread, TaskCreationOptions.LongRunning); + t.Wait(); + } + catch (AggregateException ex) + { + throw ex.InnerException; + } + } + + private void LoadAssemblyOnBackgroundThread() + { + Assembly.Load(new byte[1000]); + } + + public void GetObjectData(SerializationInfo info, StreamingContext context) + { + } + } +} diff --git a/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj b/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj index 39715dde7e29..1f248fe4db78 100644 --- a/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj +++ b/src/System.Runtime.Serialization.Formatters/tests/System.Runtime.Serialization.Formatters.Tests.csproj @@ -4,7 +4,7 @@ true true true - netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release;netcoreapp-Debug;netcoreapp-Release + netcoreapp-Debug;netcoreapp-Release;netfx-Debug;netfx-Release;netstandard-Debug;netstandard-Release @@ -17,6 +17,7 @@ + diff --git a/src/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj b/src/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj index fb7cdf97b376..abfe8a5ea145 100644 --- a/src/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj +++ b/src/System.Runtime.WindowsRuntime/src/System.Runtime.WindowsRuntime.csproj @@ -36,17 +36,19 @@ - System.Private.Interop + Alias_System_Private_Interop - System.Private.Interop + Alias_System_Private_Interop - System.Private.Interop + Alias_System_Private_Interop - + + Alias_System_Private_Interop + diff --git a/src/System.Runtime/ref/Configurations.props b/src/System.Runtime/ref/Configurations.props index 78fe5293a554..2b2323d47917 100644 --- a/src/System.Runtime/ref/Configurations.props +++ b/src/System.Runtime/ref/Configurations.props @@ -2,7 +2,6 @@ netcoreapp; - netcoreappaot; uap; diff --git a/src/System.Runtime/ref/System.Runtime.cs b/src/System.Runtime/ref/System.Runtime.cs index 654b711ca4f0..1218e1275a51 100644 --- a/src/System.Runtime/ref/System.Runtime.cs +++ b/src/System.Runtime/ref/System.Runtime.cs @@ -1724,7 +1724,7 @@ protected MemberAccessException(System.Runtime.Serialization.SerializationInfo i public MemberAccessException(string message) { } public MemberAccessException(string message, System.Exception inner) { } } - public readonly partial struct Memory + public readonly partial struct Memory : IEquatable> { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -1732,7 +1732,6 @@ public readonly partial struct Memory public Memory(T[] array, int start, int length) { throw null; } public static System.Memory Empty { get { throw null; } } public bool IsEmpty { get { throw null; } } - public System.Memory this[System.Range range] { get { throw null; } } public int Length { get { throw null; } } public System.Span Span { get { throw null; } } public void CopyTo(System.Memory destination) { } @@ -1745,10 +1744,8 @@ public void CopyTo(System.Memory destination) { } public static implicit operator System.ReadOnlyMemory (System.Memory memory) { throw null; } public static implicit operator System.Memory (T[] array) { throw null; } public System.Buffers.MemoryHandle Pin() { throw null; } - public System.Memory Slice(System.Index startIndex) { throw null; } public System.Memory Slice(int start) { throw null; } public System.Memory Slice(int start, int length) { throw null; } - public System.Memory Slice(System.Range range) { throw null; } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Memory destination) { throw null; } @@ -1984,17 +1981,9 @@ public PlatformNotSupportedException(string message, System.Exception inner) { } public override bool Equals(object value) { throw null; } public bool Equals(System.Range other) { throw null; } public override int GetHashCode() { throw null; } - public System.Range.OffsetAndLength GetOffsetAndLength(int length) { throw null; } + public (int Offset, int Length) GetOffsetAndLength(int length) { throw null; } public static System.Range StartAt(System.Index start) { throw null; } public override string ToString() { throw null; } - public readonly partial struct OffsetAndLength - { - private readonly int _dummyPrimitive; - public OffsetAndLength(int offset, int length) { throw null; } - public int Length { get { throw null; } } - public int Offset { get { throw null; } } - public void Deconstruct(out int offset, out int length) { throw null; } - } } public partial class RankException : System.SystemException { @@ -2003,7 +1992,7 @@ protected RankException(System.Runtime.Serialization.SerializationInfo info, Sys public RankException(string message) { } public RankException(string message, System.Exception innerException) { } } - public readonly partial struct ReadOnlyMemory + public readonly partial struct ReadOnlyMemory : IEquatable> { private readonly object _dummy; private readonly int _dummyPrimitive; @@ -2011,7 +2000,6 @@ public readonly partial struct ReadOnlyMemory public ReadOnlyMemory(T[] array, int start, int length) { throw null; } public static System.ReadOnlyMemory Empty { get { throw null; } } public bool IsEmpty { get { throw null; } } - public System.ReadOnlyMemory this[System.Range range] { get { throw null; } } public int Length { get { throw null; } } public System.ReadOnlySpan Span { get { throw null; } } public void CopyTo(System.Memory destination) { } @@ -2023,10 +2011,8 @@ public void CopyTo(System.Memory destination) { } public static implicit operator System.ReadOnlyMemory (System.ArraySegment segment) { throw null; } public static implicit operator System.ReadOnlyMemory (T[] array) { throw null; } public System.Buffers.MemoryHandle Pin() { throw null; } - public System.ReadOnlyMemory Slice(System.Index startIndex) { throw null; } public System.ReadOnlyMemory Slice(int start) { throw null; } public System.ReadOnlyMemory Slice(int start, int length) { throw null; } - public System.ReadOnlyMemory Slice(System.Range range) { throw null; } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Memory destination) { throw null; } @@ -2041,9 +2027,7 @@ public readonly ref partial struct ReadOnlySpan public ReadOnlySpan(T[] array, int start, int length) { throw null; } public static System.ReadOnlySpan Empty { get { throw null; } } public bool IsEmpty { get { throw null; } } - public ref readonly T this[System.Index index] { get { throw null; } } public ref readonly T this[int index] { get { throw null; } } - public System.ReadOnlySpan this[System.Range range] { get { throw null; } } public int Length { get { throw null; } } public void CopyTo(System.Span destination) { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] @@ -2059,10 +2043,8 @@ public void CopyTo(System.Span destination) { } public static implicit operator System.ReadOnlySpan (System.ArraySegment segment) { throw null; } public static implicit operator System.ReadOnlySpan (T[] array) { throw null; } public static bool operator !=(System.ReadOnlySpan left, System.ReadOnlySpan right) { throw null; } - public System.ReadOnlySpan Slice(System.Index startIndex) { throw null; } public System.ReadOnlySpan Slice(int start) { throw null; } public System.ReadOnlySpan Slice(int start, int length) { throw null; } - public System.ReadOnlySpan Slice(System.Range range) { throw null; } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } @@ -2241,9 +2223,7 @@ public readonly ref partial struct Span public Span(T[] array, int start, int length) { throw null; } public static System.Span Empty { get { throw null; } } public bool IsEmpty { get { throw null; } } - public ref T this[System.Index index] { get { throw null; } } public ref T this[int index] { get { throw null; } } - public System.Span this[System.Range range] { get { throw null; } } public int Length { get { throw null; } } public void Clear() { } public void CopyTo(System.Span destination) { } @@ -2262,10 +2242,8 @@ public void Fill(T value) { } public static implicit operator System.ReadOnlySpan (System.Span span) { throw null; } public static implicit operator System.Span (T[] array) { throw null; } public static bool operator !=(System.Span left, System.Span right) { throw null; } - public System.Span Slice(System.Index startIndex) { throw null; } public System.Span Slice(int start) { throw null; } public System.Span Slice(int start, int length) { throw null; } - public System.Span Slice(System.Range range) { throw null; } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } @@ -2306,11 +2284,7 @@ public unsafe String(sbyte* value, int startIndex, int length) { } [System.CLSCompliantAttribute(false)] public unsafe String(sbyte* value, int startIndex, int length, System.Text.Encoding enc) { } [System.Runtime.CompilerServices.IndexerName("Chars")] - public char this[System.Index index] { get { throw null; } } - [System.Runtime.CompilerServices.IndexerName("Chars")] public char this[int index] { get { throw null; } } - [System.Runtime.CompilerServices.IndexerName("Chars")] - public string this[System.Range range] { get { throw null; } } public int Length { get { throw null; } } public object Clone() { throw null; } public static int Compare(System.String strA, int indexA, System.String strB, int indexB, int length) { throw null; } @@ -2372,6 +2346,8 @@ public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, in public static int GetHashCode(System.ReadOnlySpan value) { throw null; } public static int GetHashCode(System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public int GetHashCode(System.StringComparison comparisonType) { throw null; } + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] + public ref readonly char GetPinnableReference() { throw null; } public System.TypeCode GetTypeCode() { throw null; } public int IndexOf(char value) { throw null; } public int IndexOf(char value, int startIndex) { throw null; } @@ -2443,10 +2419,8 @@ public void CopyTo(int sourceIndex, char[] destination, int destinationIndex, in public bool StartsWith(System.String value) { throw null; } public bool StartsWith(System.String value, bool ignoreCase, System.Globalization.CultureInfo culture) { throw null; } public bool StartsWith(System.String value, System.StringComparison comparisonType) { throw null; } - public System.String Substring(System.Index startIndex) { throw null; } public System.String Substring(int startIndex) { throw null; } public System.String Substring(int startIndex, int length) { throw null; } - public System.String Substring(System.Range range) { throw null; } System.Collections.Generic.IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() { throw null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { throw null; } bool System.IConvertible.ToBoolean(System.IFormatProvider provider) { throw null; } @@ -7111,6 +7085,12 @@ public ObjectHandle(object o) { } } namespace System.Runtime.Serialization { + public sealed partial class DeserializationBlockedException : System.Exception + { + public DeserializationBlockedException() { } + public DeserializationBlockedException(String message) { } + public DeserializationBlockedException(Exception innerException) { } + } public partial interface IDeserializationCallback { void OnDeserialization(object sender); diff --git a/src/System.Runtime/ref/System.Runtime.csproj b/src/System.Runtime/ref/System.Runtime.csproj index a5c288fa2756..3db39a04ef8e 100644 --- a/src/System.Runtime/ref/System.Runtime.csproj +++ b/src/System.Runtime/ref/System.Runtime.csproj @@ -6,7 +6,7 @@ {ADBCF120-3454-4A3C-9D1D-AC4293E795D6} $(NoWarn);0809;0618 - netcoreapp-Debug;netcoreapp-Release;netcoreappaot-Debug;netcoreappaot-Release;uap-Debug;uap-Release + netcoreapp-Debug;netcoreapp-Release;uap-Debug;uap-Release diff --git a/src/System.Runtime/src/ApiCompatBaseline.uapaot.txt b/src/System.Runtime/src/ApiCompatBaseline.uapaot.txt index aed70eb3ba33..7e2b2e126637 100644 --- a/src/System.Runtime/src/ApiCompatBaseline.uapaot.txt +++ b/src/System.Runtime/src/ApiCompatBaseline.uapaot.txt @@ -3,6 +3,7 @@ TypesMustExist : Type 'System.ArgIterator' does not exist in the implementation MembersMustExist : Member 'System.String System.Runtime.CompilerServices.RuntimeFeature.DefaultImplementationsOfInterfaces' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(System.RuntimeMethodHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(System.RuntimeMethodHandle, System.RuntimeTypeHandle[])' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.GetPinnableReference()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Text.Rune.DecodeFromUtf16(System.ReadOnlySpan, System.Text.Rune, System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Text.Rune.DecodeLastFromUtf16(System.ReadOnlySpan, System.Text.Rune, System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Text.Rune.DecodeFromUtf8(System.ReadOnlySpan, System.Text.Rune, System.Int32)' does not exist in the implementation but it does exist in the contract. @@ -11,3 +12,7 @@ MembersMustExist : Member 'System.Text.Rune.EncodeToUtf16(System.Span, System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Text.Rune.EncodeToUtf8(System.Span)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Text.Rune.TryEncodeToUtf8(System.Span, System.Int32)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Range.GetOffsetAndLength(System.Int32)' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Range.OffsetAndLength' does not exist in the implementation but it does exist in the contract. +CannotRemoveBaseTypeOrInterface : Type 'System.Memory' does not implement interface 'System.IEquatable>' in the implementation but it does in the contract. +CannotRemoveBaseTypeOrInterface : Type 'System.ReadOnlyMemory' does not implement interface 'System.IEquatable>' in the implementation but it does in the contract. diff --git a/src/System.Runtime/src/Configurations.props b/src/System.Runtime/src/Configurations.props index 78e091b28e29..0adb3f76b008 100644 --- a/src/System.Runtime/src/Configurations.props +++ b/src/System.Runtime/src/Configurations.props @@ -3,8 +3,6 @@ uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; diff --git a/src/System.Runtime/src/System.Runtime.csproj b/src/System.Runtime/src/System.Runtime.csproj index 0e366eb48fb3..e3808c9f01e0 100644 --- a/src/System.Runtime/src/System.Runtime.csproj +++ b/src/System.Runtime/src/System.Runtime.csproj @@ -4,8 +4,12 @@ System.Runtime true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + + + + @@ -16,6 +20,7 @@ + diff --git a/src/System.Runtime/src/System/System.Runtime.Typeforwards.cs b/src/System.Runtime/src/System/System.Runtime.Typeforwards.cs new file mode 100644 index 000000000000..16f969b9b326 --- /dev/null +++ b/src/System.Runtime/src/System/System.Runtime.Typeforwards.cs @@ -0,0 +1 @@ +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] \ No newline at end of file diff --git a/src/System.Runtime/tests/System.Runtime.Tests.csproj b/src/System.Runtime/tests/System.Runtime.Tests.csproj index 4b97b787c420..812406b043d9 100644 --- a/src/System.Runtime/tests/System.Runtime.Tests.csproj +++ b/src/System.Runtime/tests/System.Runtime.Tests.csproj @@ -287,9 +287,11 @@ + + @@ -338,4 +340,4 @@ - + \ No newline at end of file diff --git a/src/System.Runtime/tests/System/BufferTests.cs b/src/System.Runtime/tests/System/BufferTests.cs index 97d2294fa166..b2b747c13401 100644 --- a/src/System.Runtime/tests/System/BufferTests.cs +++ b/src/System.Runtime/tests/System/BufferTests.cs @@ -38,7 +38,7 @@ public static void BlockCopy_Invalid() AssertExtensions.Throws("count", () => Buffer.BlockCopy(new byte[3], 0, new byte[3], 0, -1)); // Count < 0 AssertExtensions.Throws("src", () => Buffer.BlockCopy(new string[3], 0, new byte[3], 0, 0)); // Src is not a byte array - AssertExtensions.Throws("dest", () => Buffer.BlockCopy(new byte[3], 0, new string[3], 0, 0)); // Dst is not a byte array + AssertExtensions.Throws("dst", "dest", () => Buffer.BlockCopy(new byte[3], 0, new string[3], 0, 0)); // Dst is not a byte array AssertExtensions.Throws("", () => Buffer.BlockCopy(new byte[3], 3, new byte[3], 0, 1)); // SrcOffset + count >= src.length AssertExtensions.Throws("", () => Buffer.BlockCopy(new byte[3], 4, new byte[3], 0, 0)); // SrcOffset >= src.Length diff --git a/src/System.Runtime/tests/System/DecimalTests.cs b/src/System.Runtime/tests/System/DecimalTests.cs index 31a23a56f112..3ac21fcee111 100644 --- a/src/System.Runtime/tests/System/DecimalTests.cs +++ b/src/System.Runtime/tests/System/DecimalTests.cs @@ -15,97 +15,239 @@ namespace System.Tests public partial class DecimalTests { [Fact] - public static void MaxValue() + public void MaxValue_Get_ReturnsExpected() { Assert.Equal(79228162514264337593543950335m, decimal.MaxValue); } [Fact] - public static void MinusOne() + public void MinusOne_Get_ReturnsExpected() { Assert.Equal(-1, decimal.MinusOne); } [Fact] - public static void MinValue() + public void MinValue_GetReturnsExpected() { Assert.Equal(-79228162514264337593543950335m, decimal.MinValue); } [Fact] - public static void One() + public static void One_Get_ReturnsExpected() { Assert.Equal(1, decimal.One); } [Fact] - public static void Zero() + public static void Zero_Get_ReturnsExpected() { Assert.Equal(0, decimal.Zero); } - [Fact] - public static void Ctor_ULong() + [Theory] + [InlineData(0)] + [InlineData(ulong.MaxValue)] + public static void Ctor_ULong(ulong value) { - Assert.Equal(ulong.MaxValue, new decimal(ulong.MaxValue)); + Assert.Equal(value, new decimal(value)); } - [Fact] - public static void Ctor_UInt() + [Theory] + [InlineData(0)] + [InlineData(uint.MaxValue)] + public static void Ctor_UInt(uint value) + { + Assert.Equal(value, new decimal(value)); + } + + public static IEnumerable Ctor_Float_TestData() + { + yield return new object[] { 123456789.123456f, new int[] { 123456800, 0, 0, 0 } }; + yield return new object[] { 2.0123456789123456f, new int[] { 2012346, 0, 0, 393216 } }; + yield return new object[] { 2E-28f, new int[] { 2, 0, 0, 1835008 } }; + yield return new object[] { 2E-29f, new int[] { 0, 0, 0, 0 } }; + yield return new object[] { 2E28f, new int[] { 536870912, 2085225666, 1084202172, 0 } }; + yield return new object[] { 1.5f, new int[] { 15, 0, 0, 65536 } }; + yield return new object[] { 0f, new int[] { 0, 0, 0, 0 } }; + yield return new object[] { float.Parse("-0.0", CultureInfo.InvariantCulture), new int[] { 0, 0, 0, 0 } }; + + yield return new object[] { 1000000.1f, new int[] { 1000000, 0, 0, 0 } }; + yield return new object[] { 100000.1f, new int[] { 1000001, 0, 0, 65536 } }; + yield return new object[] { 10000.1f, new int[] { 100001, 0, 0, 65536 } }; + yield return new object[] { 1000.1f, new int[] { 10001, 0, 0, 65536 } }; + yield return new object[] { 100.1f, new int[] { 1001, 0, 0, 65536 } }; + yield return new object[] { 10.1f, new int[] { 101, 0, 0, 65536 } }; + yield return new object[] { 1.1f, new int[] { 11, 0, 0, 65536 } }; + yield return new object[] { 1f, new int[] { 1, 0, 0, 0 } }; + yield return new object[] { 0.1f, new int[] { 1, 0, 0, 65536 } }; + yield return new object[] { 0.01f, new int[] { 10, 0, 0, 196608 } }; + yield return new object[] { 0.001f, new int[] { 1, 0, 0, 196608 } }; + yield return new object[] { 0.0001f, new int[] { 10, 0, 0, 327680 } }; + yield return new object[] { 0.00001f, new int[] { 10, 0, 0, 393216 } }; + yield return new object[] { 0.0000000000000000000000000001f, new int[] { 1, 0, 0, 1835008 } }; + yield return new object[] { 0.00000000000000000000000000001f, new int[] { 0, 0, 0, 0 } }; + } + + [Theory] + [MemberData(nameof(Ctor_Float_TestData))] + public void Ctor_Float(float value, int[] bits) { - Assert.Equal(uint.MaxValue, new decimal(uint.MaxValue)); + var d = new decimal(value); + Assert.Equal((decimal)value, d); + Assert.Equal(bits, Decimal.GetBits(d)); } - [Fact] - public static void Ctor_Float() + [Theory] + [InlineData(2E29)] + [InlineData(float.NaN)] + [InlineData(float.MaxValue)] + [InlineData(float.MinValue)] + [InlineData(float.PositiveInfinity)] + [InlineData(float.NegativeInfinity)] + public void Ctor_InvalidFloat_ThrowsOverlowException(float value) { - Assert.Equal((decimal)((float)123456789.123456), new decimal((float)123456789.123456)); + Assert.Throws(() => new decimal(value)); } - [Fact] - public static void Ctor_Long() + [Theory] + [InlineData(long.MinValue)] + [InlineData(0)] + [InlineData(long.MaxValue)] + public void Ctor_Long(long value) { - Assert.Equal(long.MaxValue, new decimal(long.MaxValue)); + Assert.Equal(value, new decimal(value)); } - [Fact] - public static void Ctor_IntArray() + public static IEnumerable Ctor_IntArray_TestData() { - var d1 = new decimal(new int[] { 1, 1, 1, 0 }); - decimal d2 = 3; - d2 += uint.MaxValue; - d2 += ulong.MaxValue; - Assert.Equal(d2, d1); + decimal expected = 3; + expected += uint.MaxValue; + expected += ulong.MaxValue; + yield return new object[] { new int[] { 1, 1, 1, 0 }, expected }; + yield return new object[] { new int[] { 1, 0, 0, 0 }, decimal.One }; + yield return new object[] { new int[] { 1000, 0, 0, 0x30000 }, decimal.One }; + yield return new object[] { new int[] { 1000, 0, 0, unchecked((int)0x80030000) }, -decimal.One }; + yield return new object[] { new int[] { 0, 0, 0, 0x1C0000 }, decimal.Zero }; + yield return new object[] { new int[] { 1000, 0, 0, 0x1C0000 }, 0.0000000000000000000000001 }; + yield return new object[] { new int[] { 0, 0, 0, 0 }, decimal.Zero }; + yield return new object[] { new int[] { 0, 0, 0, unchecked((int)0x80000000) }, decimal.Zero }; } - [Fact] - public static void Ctor_IntArray_Invalid() + [Theory] + [MemberData(nameof(Ctor_IntArray_TestData))] + public void Ctor_IntArray(int[] value, decimal expected) { - AssertExtensions.Throws("bits", () => new decimal(null)); // Bits is null - AssertExtensions.Throws(null, () => new decimal(new int[3])); // Bits.Length is not 4 - AssertExtensions.Throws(null, () => new decimal(new int[5])); // Bits.Length is not 4 + Assert.Equal(expected, new decimal(value)); } [Fact] - public static void Ctor_Int() + public void Ctor_NullBits_ThrowsArgumentNullException() { - Assert.Equal(int.MaxValue, new decimal(int.MaxValue)); + AssertExtensions.Throws("bits", () => new decimal(null)); } - [Fact] - public static void Ctor_Double() + [Theory] + [InlineData(new int[] { 0, 0, 0 })] + [InlineData(new int[] { 0, 0, 0, 0, 0 })] + [InlineData(new int[] { 0, 0, 0, 1 })] + [InlineData(new int[] { 0, 0, 0, 0x00001 })] + [InlineData(new int[] { 0, 0, 0, 0x1D0000 })] + [InlineData(new int[] { 0, 0, 0, unchecked((int)0x40000000) })] + public void Ctor_InvalidBits_ThrowsArgumentException(int[] bits) + { + AssertExtensions.Throws(null, () => new decimal(bits)); + } + + [InlineData(int.MinValue)] + [InlineData(0)] + [InlineData(int.MaxValue)] + public void Ctor_Int(int value) + { + Assert.Equal(value, new decimal(value)); + } + + public static IEnumerable Ctor_Double_TestData() + { + yield return new object[] { 123456789.123456, new int[] { -2045800064, 28744, 0, 393216 } }; + yield return new object[] { 2.0123456789123456, new int[] { -1829795549, 46853, 0, 917504 } }; + yield return new object[] { 2E-28, new int[] { 2, 0, 0, 1835008 } }; + yield return new object[] { 2E-29, new int[] { 0, 0, 0, 0 } }; + yield return new object[] { 2E28, new int[] { 536870912, 2085225666, 1084202172, 0 } }; + yield return new object[] { 1.5, new int[] { 15, 0, 0, 65536 } }; + yield return new object[] { 0, new int[] { 0, 0, 0, 0 } }; + yield return new object[] { double.Parse("-0.0", CultureInfo.InvariantCulture), new int[] { 0, 0, 0, 0 } }; + + yield return new object[] { 100000000000000.1, new int[] { 276447232, 23283, 0, 0 } }; + yield return new object[] { 10000000000000.1, new int[] { 276447233, 23283, 0, 65536 } }; + yield return new object[] { 1000000000000.1, new int[] { 1316134913, 2328, 0, 65536 } }; + yield return new object[] { 100000000000.1, new int[] { -727379967, 232, 0, 65536 } }; + yield return new object[] { 10000000000.1, new int[] { 1215752193, 23, 0, 65536 } }; + yield return new object[] { 1000000000.1, new int[] { 1410065409, 2, 0, 65536 } }; + yield return new object[] { 100000000.1, new int[] { 1000000001, 0, 0, 65536 } }; + yield return new object[] { 10000000.1, new int[] { 100000001, 0, 0, 65536 } }; + yield return new object[] { 1000000.1, new int[] { 10000001, 0, 0, 65536 } }; + yield return new object[] { 100000.1, new int[] { 1000001, 0, 0, 65536 } }; + yield return new object[] { 10000.1, new int[] { 100001, 0, 0, 65536 } }; + yield return new object[] { 1000.1, new int[] { 10001, 0, 0, 65536 } }; + yield return new object[] { 100.1, new int[] { 1001, 0, 0, 65536 } }; + yield return new object[] { 10.1, new int[] { 101, 0, 0, 65536 } }; + yield return new object[] { 1.1, new int[] { 11, 0, 0, 65536 } }; + yield return new object[] { 1, new int[] { 1, 0, 0, 0 } }; + yield return new object[] { 0.1, new int[] { 1, 0, 0, 65536 } }; + yield return new object[] { 0.01, new int[] { 1, 0, 0, 131072 } }; + yield return new object[] { 0.001, new int[] { 1, 0, 0, 196608 } }; + yield return new object[] { 0.0001, new int[] { 1, 0, 0, 262144 } }; + yield return new object[] { 0.00001, new int[] { 1, 0, 0, 327680 } }; + yield return new object[] { 0.0000000000000000000000000001, new int[] { 1, 0, 0, 1835008 } }; + yield return new object[] { 0.00000000000000000000000000001, new int[] { 0, 0, 0, 0 } }; + } + + [Theory] + [MemberData(nameof(Ctor_Double_TestData))] + public void Ctor_Double(double value, int[] bits) + { + var d = new decimal(value); + Assert.Equal((decimal)value, d); + Assert.Equal(bits, Decimal.GetBits(d)); + } + + [Theory] + [InlineData(double.NaN)] + [InlineData(double.MaxValue)] + [InlineData(double.MinValue)] + [InlineData(double.PositiveInfinity)] + [InlineData(double.NegativeInfinity)] + public void Ctor_LargeDouble_ThrowsOverlowException(double value) + { + Assert.Throws(() => new decimal(value)); + } + + public static IEnumerable Ctor_Int_Int_Int_Bool_Byte_TestData() { - Assert.Equal((decimal)123456789.123456, new decimal(123456789.123456)); + decimal expected = 3; + expected += uint.MaxValue; + expected += ulong.MaxValue; + yield return new object[] { 1, 1, 1, false, 0, expected }; + yield return new object[] { 1, 0, 0, false, 0, decimal.One }; + yield return new object[] { 1000, 0, 0, false, 3, decimal.One }; + yield return new object[] { 1000, 0, 0, true, 3, -decimal.One }; + yield return new object[] { 0, 0, 0, false, 28, decimal.Zero }; + yield return new object[] { 1000, 0, 0, false, 28, 0.0000000000000000000000001 }; + yield return new object[] { 0, 0, 0, false, 0, decimal.Zero }; + yield return new object[] { 0, 0, 0, true, 0, decimal.Zero }; + } + + [Theory] + [MemberData(nameof(Ctor_Int_Int_Int_Bool_Byte_TestData))] + public void Ctor_Int_Int_Int_Bool_Byte(int lo, int mid, int hi, bool isNegative, byte scale, decimal expected) + { + Assert.Equal(expected, new decimal(lo, mid, hi, isNegative, scale)); } [Fact] - public static void Ctor_Int_Int_Int_Bool_Byte() + public void Ctor_LargeScale_ThrowsArgumentOutOfRangeException() { - var d1 = new decimal(1, 1, 1, false, 0); - decimal d2 = 3; - d2 += uint.MaxValue; - d2 += ulong.MaxValue; - Assert.Equal(d2, d1); + AssertExtensions.Throws("scale", () => new Decimal(1, 2, 3, false, 29)); } public static IEnumerable Add_Valid_TestData() @@ -140,7 +282,7 @@ public static void Add(decimal d1, decimal d2, decimal expected) Assert.Equal(expected, d3); } - public static IEnumerable Add_Invalid_TestData() + public static IEnumerable Add_Overflows_TestData() { yield return new object[] { decimal.MaxValue, decimal.MaxValue }; yield return new object[] { 79228162514264337593543950330m, 6 }; @@ -148,8 +290,8 @@ public static IEnumerable Add_Invalid_TestData() } [Theory] - [MemberData(nameof(Add_Invalid_TestData))] - public static void Add_Invalid(decimal d1, decimal d2) + [MemberData(nameof(Add_Overflows_TestData))] + public void Add_Overflows_ThrowsOverflowException(decimal d1, decimal d2) { Assert.Throws(() => d1 + d2); Assert.Throws(() => decimal.Add(d1, d2)); @@ -315,49 +457,59 @@ public static void Divide(decimal d1, decimal d2, decimal expected) Assert.Equal(expected, decimal.Divide(d1, d2)); } - public static IEnumerable Divide_Invalid_TestData() - { - yield return new object[] { decimal.One, decimal.Zero, typeof(DivideByZeroException) }; - yield return new object[] { decimal.Zero, decimal.Zero, typeof(DivideByZeroException) }; - yield return new object[] { 0.0m, 0.0m, typeof(DivideByZeroException) }; - - yield return new object[] { 79228162514264337593543950335m, -0.9999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 792281625142643.37593543950335m, -0.0000000000000079228162514264337593543950335m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.1m, typeof(OverflowException) }; - yield return new object[] { 7922816251426433759354395034m, 0.1m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.999999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, -0.1m, typeof(OverflowException) }; - yield return new object[] { 79228162514264337593543950335m, -0.9999999999999999999999999m, typeof(OverflowException) }; - yield return new object[] { decimal.MaxValue / 2m, 0.5m, typeof(OverflowException) }; + public static IEnumerable Divide_ZeroDenominator_TestData() + { + yield return new object[] { decimal.One, decimal.Zero }; + yield return new object[] { decimal.Zero, decimal.Zero }; + yield return new object[] { 0.0m, 0.0m }; + } + + [Theory] + [MemberData(nameof(Divide_ZeroDenominator_TestData))] + public void Divide_ZeroDenominator_ThrowsDivideByZeroException(decimal d1, decimal d2) + { + Assert.Throws(() => d1 / d2); + Assert.Throws(() => decimal.Divide(d1, d2)); + } + public static IEnumerable Divide_Overflows_TestData() + { + yield return new object[] { 79228162514264337593543950335m, -0.9999999999999999999999999m }; + yield return new object[] { 792281625142643.37593543950335m, -0.0000000000000079228162514264337593543950335m }; + yield return new object[] { 79228162514264337593543950335m, 0.1m }; + yield return new object[] { 7922816251426433759354395034m, 0.1m }; + yield return new object[] { 79228162514264337593543950335m, 0.9m }; + yield return new object[] { 79228162514264337593543950335m, 0.99m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999m }; + yield return new object[] { 79228162514264337593543950335m, 0.999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.99999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.999999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.999999999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, 0.9999999999999999999999999999m }; + yield return new object[] { 79228162514264337593543950335m, -0.1m }; + yield return new object[] { 79228162514264337593543950335m, -0.9999999999999999999999999m }; + yield return new object[] { decimal.MaxValue / 2m, 0.5m }; } [Theory] - [MemberData(nameof(Divide_Invalid_TestData))] - public static void Divide_Invalid(decimal d1, decimal d2, Type exceptionType) + [MemberData(nameof(Divide_Overflows_TestData))] + public void Divide_Overflows_ThrowsOverflowException(decimal d1, decimal d2) { - Assert.Throws(exceptionType, () => d1 / d2); - Assert.Throws(exceptionType, () => decimal.Divide(d1, d2)); + Assert.Throws(() => d1 / d2); + Assert.Throws(() => decimal.Divide(d1, d2)); } public static IEnumerable Equals_TestData() @@ -794,15 +946,15 @@ public static void RemainderV2(decimal d1, decimal d2, decimal expected) public static IEnumerable Remainder_Invalid_TestData() { - yield return new object[] { 5m, 0m, typeof(DivideByZeroException) }; + yield return new object[] { 5m, 0m }; } [Theory] [MemberData(nameof(Remainder_Invalid_TestData))] - public static void Remainder_Invalid(decimal d1, decimal d2, Type exceptionType) + public static void Remainder_ZeroDenominator_ThrowsDivideByZeroException(decimal d1, decimal d2) { - Assert.Throws(exceptionType, () => d1 % d2); - Assert.Throws(exceptionType, () => decimal.Remainder(d1, d2)); + Assert.Throws(() => d1 % d2); + Assert.Throws(() => decimal.Remainder(d1, d2)); } public static IEnumerable Round_Valid_TestData() @@ -1280,8 +1432,8 @@ private static void ToString(decimal f, string format, IFormatProvider provider, public static void ToString_InvalidFormat_ThrowsFormatException() { decimal f = 123; - Assert.Throws(() => f.ToString("Y")); // Invalid format - Assert.Throws(() => f.ToString("Y", null)); // Invalid format + Assert.Throws(() => f.ToString("Y")); + Assert.Throws(() => f.ToString("Y", null)); } public static IEnumerable Truncate_TestData() diff --git a/src/System.Runtime/tests/System/IndexTests.cs b/src/System.Runtime/tests/System/IndexTests.cs index 8f8d88083a1f..e7a0b5071bb6 100644 --- a/src/System.Runtime/tests/System/IndexTests.cs +++ b/src/System.Runtime/tests/System/IndexTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using Xunit; namespace System.Tests @@ -111,5 +112,23 @@ public static void ToStringTest() index1 = new Index(50, fromEnd: true); Assert.Equal("^" + 50.ToString(), index1.ToString()); } + + [Fact] + public static void CollectionTest() + { + int [] array = new int [] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + List list = new List(array); + + for (int i = 0; i < list.Count; i++) + { + Assert.Equal(i, list[Index.FromStart(i)]); + Assert.Equal(list.Count - i - 1, list[^(i + 1)]); + + Assert.Equal(i, array[Index.FromStart(i)]); + Assert.Equal(list.Count - i - 1, array[^(i + 1)]); + + Assert.Equal(array.AsSpan().Slice(i, array.Length - i).ToArray(), array[i..]); + } + } } } diff --git a/src/System.Runtime/tests/System/RangeTests.cs b/src/System.Runtime/tests/System/RangeTests.cs index 16002b8ef9c9..4ea37914a5ba 100644 --- a/src/System.Runtime/tests/System/RangeTests.cs +++ b/src/System.Runtime/tests/System/RangeTests.cs @@ -103,5 +103,68 @@ public static void ToStringTest() range1 = new Range(new Index(10, fromEnd: false), new Index(20, fromEnd: true)); Assert.Equal(10.ToString() + "..^" + 20.ToString(), range1.ToString()); } + + [Fact] + public static void CustomTypeTest() + { + CustomRangeTester crt = new CustomRangeTester(new int [] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}); + for (int i = 0; i < crt.Length; i++) + { + Assert.Equal(crt[i], crt[Index.FromStart(i)]); + Assert.Equal(crt[crt.Length - i - 1], crt[^(i + 1)]); + + Assert.True(crt.Slice(i, crt.Length - i).Equals(crt[i..^0]), $"Index = {i} and {crt.Slice(i, crt.Length - i)} != {crt[i..^0]}"); + } + } + + // CustomRangeTester is a custom class which containing the members Length, Slice and int indexer. + // Having these members allow the C# compiler to support + // this[Index] + // this[Range] + private class CustomRangeTester : IEquatable + { + private int [] _data; + + public CustomRangeTester(int [] data) => _data = data; + public int Length => _data.Length; + public int this[int index] => _data[index]; + public CustomRangeTester Slice(int start, int length) => new CustomRangeTester(_data.AsSpan().Slice(start, length).ToArray()); + + public int [] Data => _data; + + public bool Equals (CustomRangeTester other) + { + if (_data.Length == other.Data.Length) + { + for (int i = 0; i < _data.Length; i++) + { + if (_data[i] != other.Data[i]) + { + return false; + } + } + return true; + } + + return false; + } + + public override string ToString() + { + if (Length == 0) + { + return "[]"; + } + + string s = "[" + _data[0]; + + for (int i = 1; i < Length; i++) + { + s = s + ", " + _data[i]; + } + + return s + "]"; + } + } } } diff --git a/src/System.Runtime/tests/System/Reflection/IsCollectibleTests.cs b/src/System.Runtime/tests/System/Reflection/IsCollectibleTests.cs index 960a371d3a6b..25033169a153 100644 --- a/src/System.Runtime/tests/System/Reflection/IsCollectibleTests.cs +++ b/src/System.Runtime/tests/System/Reflection/IsCollectibleTests.cs @@ -53,9 +53,7 @@ public void Assembly_IsCollectibleFalse_WhenUsingAssemblyLoad() Assert.Contains("\"Default\"", alc.ToString()); Assert.Contains("System.Runtime.Loader.DefaultAssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Contains(asm, alc.Assemblies); -#endif return RemoteExecutor.SuccessExitCode; }).Dispose(); @@ -78,9 +76,7 @@ public void Assembly_IsCollectibleFalse_WhenUsingAssemblyLoadContext() Assert.Contains("Assembly_IsCollectibleFalse_WhenUsingAssemblyLoadContext", alc.ToString()); Assert.Contains("System.Runtime.Loader.AssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Contains(asm, alc.Assemblies); -#endif return RemoteExecutor.SuccessExitCode; }).Dispose(); @@ -103,9 +99,7 @@ public void Assembly_IsCollectibleTrue_WhenUsingTestAssemblyLoadContext() Assert.Contains("\"\"", alc.ToString()); Assert.Contains("System.Reflection.Tests.TestAssemblyLoadContext", alc.ToString()); Assert.Contains(alc, AssemblyLoadContext.All); -#if CoreCLR_23583 Assert.Contains(asm, alc.Assemblies); -#endif return RemoteExecutor.SuccessExitCode; }).Dispose(); diff --git a/src/System.Runtime/tests/System/StringTests.netcoreapp.cs b/src/System.Runtime/tests/System/StringTests.netcoreapp.cs index 69a5bdd42d28..84be52acee89 100644 --- a/src/System.Runtime/tests/System/StringTests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/StringTests.netcoreapp.cs @@ -6,6 +6,8 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Microsoft.DotNet.RemoteExecutor; @@ -836,6 +838,84 @@ public static void GetHashCode_NoSuchStringComparison_ThrowsArgumentException(St AssertExtensions.Throws("comparisonType", () => string.GetHashCode("abc".AsSpan(), comparisonType)); } + [Theory] + [InlineData("")] // empty string + [InlineData("hello")] // non-empty string + public unsafe static void GetPinnableReference_ReturnsSameAsGCHandleAndLegacyFixed(string input) + { + Assert.NotNull(input); // test shouldn't have null input + + // First, ensure the value pointed to by GetPinnableReference is correct. + // It should point to the first character (or the null terminator for empty inputs). + + ref readonly char rChar = ref input.GetPinnableReference(); + Assert.Equal((input.Length > 0) ? input[0] : '\0', rChar); + + // Next, ensure that GetPinnableReference() and GCHandle.AddrOfPinnedObject agree + // on the address being returned. + + GCHandle gcHandle = GCHandle.Alloc(input, GCHandleType.Pinned); + try + { + Assert.Equal((IntPtr)Unsafe.AsPointer(ref Unsafe.AsRef(in rChar)), gcHandle.AddrOfPinnedObject()); + } + finally + { + gcHandle.Free(); + } + + // Next, ensure that GetPinnableReference matches the string projected as a ROS. + + Assert.True(Unsafe.AreSame(ref Unsafe.AsRef(in rChar), ref MemoryMarshal.GetReference((ReadOnlySpan)input))); + + // Finally, ensure that GetPinnableReference matches the legacy 'fixed' keyword. + + DynamicMethod dynamicMethod = new DynamicMethod("tester", typeof(bool), new[] { typeof(string) }); + ILGenerator ilGen = dynamicMethod.GetILGenerator(); + LocalBuilder pinnedLocal = ilGen.DeclareLocal(typeof(object), pinned: true); + + ilGen.Emit(OpCodes.Ldarg_0); // load 'input' and pin it + ilGen.Emit(OpCodes.Stloc, pinnedLocal); + + ilGen.Emit(OpCodes.Ldloc, pinnedLocal); // get the address of field 0 from pinned 'input' + ilGen.Emit(OpCodes.Conv_I); + + ilGen.Emit(OpCodes.Call, typeof(RuntimeHelpers).GetProperty("OffsetToStringData").GetMethod); // get pointer to start of string data + ilGen.Emit(OpCodes.Add); + + ilGen.Emit(OpCodes.Ldarg_0); // get value of input.GetPinnableReference() + ilGen.Emit(OpCodes.Callvirt, typeof(string).GetMethod("GetPinnableReference")); + + // At this point, the top of the evaluation stack is traditional (fixed char* = input) and input.GetPinnableReference(). + // Compare for equality and return. + + ilGen.Emit(OpCodes.Ceq); + ilGen.Emit(OpCodes.Ret); + + Assert.True((bool)dynamicMethod.Invoke(null, new[] { input })); + } + + [Fact] + public unsafe static void GetPinnableReference_WithNullInput_ThrowsNullRef() + { + // This test uses an explicit call instead of the normal callvirt that C# would emit. + // This allows us to make sure the NullReferenceException is coming from *within* + // the GetPinnableReference method rather than on the call site to that method. + + DynamicMethod dynamicMethod = new DynamicMethod("tester", typeof(void), Type.EmptyTypes); + ILGenerator ilGen = dynamicMethod.GetILGenerator(); + + ilGen.Emit(OpCodes.Ldnull); + ilGen.Emit(OpCodes.Call, typeof(string).GetMethod("GetPinnableReference")); + ilGen.Emit(OpCodes.Pop); + ilGen.Emit(OpCodes.Ret); + + Action del = (Action)dynamicMethod.CreateDelegate(typeof(Action)); + + Assert.NotNull(del); + Assert.Throws(del); + } + [Theory] [InlineData("")] [InlineData("a")] @@ -1071,51 +1151,51 @@ public static void Concat_Spans(string[] values, string expected) Assert.Equal(expected, result); } - // [Fact] - // public static void IndexerUsingIndexTest() - // { - // Index index; - // string s = "0123456789ABCDEF"; + [Fact] + public static void IndexerUsingIndexTest() + { + Index index; + string s = "0123456789ABCDEF"; - // for (int i = 0; i < s.Length; i++) - // { - // index = Index.FromStart(i); - // Assert.Equal(s[i], s[index]); + for (int i = 0; i < s.Length; i++) + { + index = Index.FromStart(i); + Assert.Equal(s[i], s[index]); - // index = Index.FromEnd(i + 1); - // Assert.Equal(s[s.Length - i - 1], s[index]); - // } + index = Index.FromEnd(i + 1); + Assert.Equal(s[s.Length - i - 1], s[index]); + } - // index = Index.FromStart(s.Length + 1); - // char c; - // Assert.Throws(() => c = s[index]); + index = Index.FromStart(s.Length + 1); + char c; + Assert.Throws(() => c = s[index]); - // index = Index.FromEnd(s.Length + 1); - // Assert.Throws(() => c = s[index]); - // } + index = Index.FromEnd(s.Length + 1); + Assert.Throws(() => c = s[index]); + } - // [Fact] - // public static void IndexerUsingRangeTest() - // { - // Range range; - // string s = "0123456789ABCDEF"; + [Fact] + public static void IndexerUsingRangeTest() + { + Range range; + string s = "0123456789ABCDEF"; - // for (int i = 0; i < s.Length; i++) - // { - // range = new Range(Index.FromStart(0), Index.FromStart(i)); - // Assert.Equal(s.Substring(0, i), s[range]); + for (int i = 0; i < s.Length; i++) + { + range = new Range(Index.FromStart(0), Index.FromStart(i)); + Assert.Equal(s.Substring(0, i), s[range]); - // range = new Range(Index.FromEnd(s.Length), Index.FromEnd(i)); - // Assert.Equal(s.Substring(0, s.Length - i), s[range]); - // } + range = new Range(Index.FromEnd(s.Length), Index.FromEnd(i)); + Assert.Equal(s.Substring(0, s.Length - i), s[range]); + } - // range = new Range(Index.FromStart(s.Length - 2), Index.FromStart(s.Length + 1)); - // string s1; - // Assert.Throws(() => s1 = s[range]); + range = new Range(Index.FromStart(s.Length - 2), Index.FromStart(s.Length + 1)); + string s1; + Assert.Throws(() => s1 = s[range]); - // range = new Range(Index.FromEnd(s.Length + 1), Index.FromEnd(0)); - // Assert.Throws(() => s1 = s[range]); - // } + range = new Range(Index.FromEnd(s.Length + 1), Index.FromEnd(0)); + Assert.Throws(() => s1 = s[range]); + } [Fact] public static void SubstringUsingIndexTest() @@ -1124,15 +1204,15 @@ public static void SubstringUsingIndexTest() for (int i = 0; i < s.Length; i++) { - Assert.Equal(s.Substring(i), s.Substring(Index.FromStart(i))); - Assert.Equal(s.Substring(s.Length - i - 1), s.Substring(Index.FromEnd(i + 1))); + Assert.Equal(s.Substring(i), s[i..]); + Assert.Equal(s.Substring(s.Length - i - 1), s[^(i + 1)..]); } // String.Substring allows the string length as a valid input. - Assert.Equal(s.Substring(s.Length), s.Substring(Index.FromStart(s.Length))); + Assert.Equal(s.Substring(s.Length), s[s.Length..]); - Assert.Throws(() => s.Substring(Index.FromStart(s.Length + 1))); - Assert.Throws(() => s.Substring(Index.FromEnd(s.Length + 1))); + Assert.Throws(() => s[(s.Length + 1)..]); + Assert.Throws(() => s[^(s.Length + 1)..]); } [Fact] @@ -1144,18 +1224,18 @@ public static void SubstringUsingRangeTest() for (int i = 0; i < s.Length; i++) { range = new Range(Index.FromStart(0), Index.FromStart(i)); - Assert.Equal(s.Substring(0, i), s.Substring(range)); + Assert.Equal(s.Substring(0, i), s[range]); range = new Range(Index.FromEnd(s.Length), Index.FromEnd(i)); - Assert.Equal(s.Substring(0, s.Length - i), s.Substring(range)); + Assert.Equal(s.Substring(0, s.Length - i), s[range]); } range = new Range(Index.FromStart(s.Length - 2), Index.FromStart(s.Length + 1)); string s1; - Assert.Throws(() => s1 = s.Substring(range)); + Assert.Throws(() => s1 = s[range]); range = new Range(Index.FromEnd(s.Length + 1), Index.FromEnd(0)); - Assert.Throws(() => s1 = s.Substring(range)); + Assert.Throws(() => s1 = s[range]); } /// @@ -1180,5 +1260,391 @@ public unsafe static bool IsSimpleActiveCodePage } } } + + [Theory] + [InlineData(" Hello ", "Hello")] + [InlineData(" \t ", "")] + [InlineData("", "")] + [InlineData(" ", "")] + public static void Trim_Memory(string s, string expected) + { + Assert.Equal(expected, s.AsSpan().Trim().ToString()); // ReadOnlySpan + Assert.Equal(expected, new Span(s.ToCharArray()).Trim().ToString()); + Assert.Equal(expected, new Memory(s.ToCharArray()).Trim().ToString()); + Assert.Equal(expected, s.AsMemory().Trim().ToString()); // ReadOnlyMemory + } + + [Theory] + [InlineData(" Hello ", " Hello")] + [InlineData(" \t ", "")] + [InlineData("", "")] + [InlineData(" ", "")] + public static void TrimEnd_Memory(string s, string expected) + { + Assert.Equal(expected, s.AsSpan().TrimEnd().ToString()); // ReadOnlySpan + Assert.Equal(expected, new Span(s.ToCharArray()).TrimEnd().ToString()); + Assert.Equal(expected, new Memory(s.ToCharArray()).TrimEnd().ToString()); + Assert.Equal(expected, s.AsMemory().TrimEnd().ToString()); // ReadOnlyMemory + } + + [Theory] + [InlineData(" Hello ", "Hello ")] + [InlineData(" \t ", "")] + [InlineData("", "")] + [InlineData(" ", "")] + public static void TrimStart_Memory(string s, string expected) + { + Assert.Equal(expected, s.AsSpan().TrimStart().ToString()); // ReadOnlySpan + Assert.Equal(expected, new Span(s.ToCharArray()).TrimStart().ToString()); + Assert.Equal(expected, new Memory(s.ToCharArray()).TrimStart().ToString()); + Assert.Equal(expected, s.AsMemory().TrimStart().ToString()); // ReadOnlyMemory + } + + [Fact] + public static void ZeroLengthTrim_Memory() + { + string s1 = string.Empty; + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.SequenceEqual(ros.Trim())); + Assert.True(ros.SequenceEqual(ros.TrimStart())); + Assert.True(ros.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(s1.ToCharArray()); + Assert.True(span.SequenceEqual(span.Trim())); + Assert.True(span.SequenceEqual(span.TrimStart())); + Assert.True(span.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(s1.ToCharArray()); + Assert.True(mem.Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(s1.ToCharArray()); + Assert.True(rom.Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimEnd().Span)); + } + + [Fact] + public static void NoWhiteSpaceTrim_Memory() + { + for (int length = 0; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + string s1 = new string(a); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.SequenceEqual(ros.Trim())); + Assert.True(ros.SequenceEqual(ros.TrimStart())); + Assert.True(ros.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(span.SequenceEqual(span.Trim())); + Assert.True(span.SequenceEqual(span.TrimStart())); + Assert.True(span.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(mem.Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(rom.Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void OnlyWhiteSpaceTrim_Memory() + { + for (int length = 0; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = ' '; + } + string s1 = new string(a); + + ReadOnlySpan ros = new ReadOnlySpan(a); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(ros.Trim())); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(ros.TrimStart())); + Assert.True(ReadOnlySpan.Empty.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(Span.Empty.SequenceEqual(span.Trim())); + Assert.True(Span.Empty.SequenceEqual(span.TrimStart())); + Assert.True(Span.Empty.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(Memory.Empty.Span.SequenceEqual(mem.Trim().Span)); + Assert.True(Memory.Empty.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(Memory.Empty.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(rom.Trim().Span)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(ReadOnlyMemory.Empty.Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void WhiteSpaceAtStartTrim_Memory() + { + for (int length = 2; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + a[0] = ' '; + string s1 = new string(a); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.Slice(1).SequenceEqual(ros.Trim())); + Assert.True(ros.Slice(1).SequenceEqual(ros.TrimStart())); + Assert.True(ros.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(span.Slice(1).SequenceEqual(span.Trim())); + Assert.True(span.Slice(1).SequenceEqual(span.TrimStart())); + Assert.True(span.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(mem.Slice(1).Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Slice(1).Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(rom.Slice(1).Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Slice(1).Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void WhiteSpaceAtEndTrim_Memory() + { + for (int length = 2; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + a[length - 1] = ' '; + string s1 = new string(a); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.Slice(0, length - 1).SequenceEqual(ros.Trim())); + Assert.True(ros.SequenceEqual(ros.TrimStart())); + Assert.True(ros.Slice(0, length - 1).SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(span.Slice(0, length - 1).SequenceEqual(span.Trim())); + Assert.True(span.SequenceEqual(span.TrimStart())); + Assert.True(span.Slice(0, length - 1).SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(mem.Slice(0, length - 1).Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Slice(0, length - 1).Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(rom.Slice(0, length - 1).Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Slice(0, length - 1).Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void WhiteSpaceAtStartAndEndTrim_Memory() + { + for (int length = 3; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + a[0] = ' '; + a[length - 1] = ' '; + string s1 = new string(a); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.Slice(1, length - 2).SequenceEqual(ros.Trim())); + Assert.True(ros.Slice(1).SequenceEqual(ros.TrimStart())); + Assert.True(ros.Slice(0, length - 1).SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(span.Slice(1, length - 2).SequenceEqual(span.Trim())); + Assert.True(span.Slice(1).SequenceEqual(span.TrimStart())); + Assert.True(span.Slice(0, length - 1).SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(mem.Slice(1, length - 2).Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Slice(1).Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Slice(0, length - 1).Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(rom.Slice(1, length - 2).Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Slice(1).Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Slice(0, length - 1).Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void WhiteSpaceInMiddleTrim_Memory() + { + for (int length = 3; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + a[1] = ' '; + string s1 = new string(a); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.SequenceEqual(ros.Trim())); + Assert.True(ros.SequenceEqual(ros.TrimStart())); + Assert.True(ros.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(a); + Assert.True(span.SequenceEqual(span.Trim())); + Assert.True(span.SequenceEqual(span.TrimStart())); + Assert.True(span.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(a); + Assert.True(mem.Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(a); + Assert.True(rom.Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimEnd().Span)); + } + } + + [Fact] + public static void TrimWhiteSpaceMultipleTimes_Memory() + { + for (int length = 3; length < 32; length++) + { + char[] a = new char[length]; + for (int i = 0; i < length; i++) + { + a[i] = 'a'; + } + a[0] = ' '; + a[length - 1] = ' '; + string s1 = new string(a); + + // ReadOnlySpan + { + ReadOnlySpan ros = s1.AsSpan(); + ReadOnlySpan trimResult = ros.Trim(); + ReadOnlySpan trimStartResult = ros.TrimStart(); + ReadOnlySpan trimEndResult = ros.TrimEnd(); + Assert.True(ros.Slice(1, length - 2).SequenceEqual(trimResult)); + Assert.True(ros.Slice(1).SequenceEqual(trimStartResult)); + Assert.True(ros.Slice(0, length - 1).SequenceEqual(trimEndResult)); + + // 2nd attempt should do nothing + Assert.True(trimResult.SequenceEqual(trimResult.Trim())); + Assert.True(trimStartResult.SequenceEqual(trimStartResult.TrimStart())); + Assert.True(trimEndResult.SequenceEqual(trimEndResult.TrimEnd())); + } + + // Span + { + Span span = new Span(a); + Span trimResult = span.Trim(); + Span trimStartResult = span.TrimStart(); + Span trimEndResult = span.TrimEnd(); + Assert.True(span.Slice(1, length - 2).SequenceEqual(trimResult)); + Assert.True(span.Slice(1).SequenceEqual(trimStartResult)); + Assert.True(span.Slice(0, length - 1).SequenceEqual(trimEndResult)); + + // 2nd attempt should do nothing + Assert.True(trimResult.SequenceEqual(trimResult.Trim())); + Assert.True(trimStartResult.SequenceEqual(trimStartResult.TrimStart())); + Assert.True(trimEndResult.SequenceEqual(trimEndResult.TrimEnd())); + } + + // Memory + { + Memory mem = new Memory(a); + Memory trimResult = mem.Trim(); + Memory trimStartResult = mem.TrimStart(); + Memory trimEndResult = mem.TrimEnd(); + Assert.True(mem.Slice(1, length - 2).Span.SequenceEqual(trimResult.Span)); + Assert.True(mem.Slice(1).Span.SequenceEqual(trimStartResult.Span)); + Assert.True(mem.Slice(0, length - 1).Span.SequenceEqual(trimEndResult.Span)); + + // 2nd attempt should do nothing + Assert.True(trimResult.Span.SequenceEqual(trimResult.Trim().Span)); + Assert.True(trimStartResult.Span.SequenceEqual(trimStartResult.TrimStart().Span)); + Assert.True(trimEndResult.Span.SequenceEqual(trimEndResult.TrimEnd().Span)); + } + + // ReadOnlyMemory + { + ReadOnlyMemory rom = new ReadOnlyMemory(a); + ReadOnlyMemory trimResult = rom.Trim(); + ReadOnlyMemory trimStartResult = rom.TrimStart(); + ReadOnlyMemory trimEndResult = rom.TrimEnd(); + Assert.True(rom.Slice(1, length - 2).Span.SequenceEqual(trimResult.Span)); + Assert.True(rom.Slice(1).Span.SequenceEqual(trimStartResult.Span)); + Assert.True(rom.Slice(0, length - 1).Span.SequenceEqual(trimEndResult.Span)); + + // 2nd attempt should do nothing + Assert.True(trimResult.Span.SequenceEqual(trimResult.Trim().Span)); + Assert.True(trimStartResult.Span.SequenceEqual(trimStartResult.TrimStart().Span)); + Assert.True(trimEndResult.Span.SequenceEqual(trimEndResult.TrimEnd().Span)); + } + } + } + + [Fact] + public static void MakeSureNoTrimChecksGoOutOfRange_Memory() + { + for (int length = 3; length < 64; length++) + { + char[] first = new char[length]; + first[0] = ' '; + first[length - 1] = ' '; + string s1 = new string(first, 1, length - 2); + + ReadOnlySpan ros = s1.AsSpan(); + Assert.True(ros.SequenceEqual(ros.Trim())); + Assert.True(ros.SequenceEqual(ros.TrimStart())); + Assert.True(ros.SequenceEqual(ros.TrimEnd())); + + Span span = new Span(s1.ToCharArray()); + Assert.True(span.SequenceEqual(span.Trim())); + Assert.True(span.SequenceEqual(span.TrimStart())); + Assert.True(span.SequenceEqual(span.TrimEnd())); + + Memory mem = new Memory(s1.ToCharArray()); + Assert.True(mem.Span.SequenceEqual(mem.Trim().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimStart().Span)); + Assert.True(mem.Span.SequenceEqual(mem.TrimEnd().Span)); + + ReadOnlyMemory rom = new ReadOnlyMemory(s1.ToCharArray()); + Assert.True(rom.Span.SequenceEqual(rom.Trim().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimStart().Span)); + Assert.True(rom.Span.SequenceEqual(rom.TrimEnd().Span)); + } + } } } diff --git a/src/System.Runtime/tests/System/Text/Unicode/Utf16UtilityTests.ValidateChars.netcoreapp.cs b/src/System.Runtime/tests/System/Text/Unicode/Utf16UtilityTests.ValidateChars.netcoreapp.cs new file mode 100644 index 000000000000..510a22cc829c --- /dev/null +++ b/src/System.Runtime/tests/System/Text/Unicode/Utf16UtilityTests.ValidateChars.netcoreapp.cs @@ -0,0 +1,267 @@ +// 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.Buffers; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +namespace System.Text.Unicode.Tests +{ + public partial class Utf16UtilityTests + { + private unsafe delegate char* GetPointerToFirstInvalidCharDel(char* pInputBuffer, int inputLength, out long utf8CodeUnitCountAdjustment, out int scalarCountAdjustment); + private static readonly Lazy _getPointerToFirstInvalidCharFn = CreateGetPointerToFirstInvalidCharFn(); + + [Theory] + [InlineData("", 0, 0)] // empty string is OK + [InlineData("X", 1, 1)] + [InlineData("XY", 2, 2)] + [InlineData("XYZ", 3, 3)] + [InlineData("", 1, 2)] + [InlineData("X", 2, 3)] + [InlineData("X", 2, 3)] + [InlineData("", 1, 3)] + [InlineData("", 1, 4)] + [InlineData("XZ", 3, 6)] + [InlineData("X<0000>Z", 3, 3)] // null chars are allowed + public void GetIndexOfFirstInvalidUtf16Sequence_WithSmallValidBuffers(string unprocessedInput, int expectedRuneCount, int expectedUtf8ByteCount) + { + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(unprocessedInput, -1 /* expectedIdxOfFirstInvalidChar */, expectedRuneCount, expectedUtf8ByteCount); + } + + [Theory] + [InlineData("", 0, 0, 0)] // standalone low surrogate (at beginning of sequence) + [InlineData("X", 1, 1, 1)] // standalone low surrogate (preceded by valid ASCII data) + [InlineData("", 1, 1, 3)] // standalone low surrogate (preceded by valid non-ASCII data) + [InlineData("", 0, 0, 0)] // standalone high surrogate (missing follow-up low surrogate) + [InlineData("Y", 0, 0, 0)] // standalone high surrogate (followed by ASCII char) + [InlineData("", 0, 0, 0)] // standalone high surrogate (followed by high surrogate) + [InlineData("", 0, 0, 0)] // standalone high surrogate (followed by valid non-ASCII char) + [InlineData("", 0, 0, 0)] // standalone low surrogate (not preceded by a high surrogate) + [InlineData("", 0, 0, 0)] // standalone low surrogate (not preceded by a high surrogate) + [InlineData("", 2, 1, 4)] // standalone low surrogate (preceded by a valid surrogate pair) + [InlineData("", 2, 1, 4)] // standalone low surrogate (preceded by a valid surrogate pair) + [InlineData("<0000>", 3, 2, 5)] // standalone low surrogate (preceded by a valid null char) + public void GetIndexOfFirstInvalidUtf16Sequence_WithSmallInvalidBuffers(string unprocessedInput, int idxOfFirstInvalidChar, int expectedRuneCount, int expectedUtf8ByteCount) + { + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(unprocessedInput, idxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + } + + [Theory] // chars below presented as hex since Xunit doesn't like invalid UTF-16 string literals + [InlineData("<2BB4><218C><1BC0><613F>", 6, 6, 18)] + [InlineData("<1854><012C><4797><41D0><5464>", 4, 4, 11)] + [InlineData("<8BD3><5037><3E3A><6336>", 4, 4, 12)] + [InlineData("<0F25><7352><4025><0B93><4107>", 2, 2, 6)] + [InlineData("<2BB4><218C><1BC0><613F>", 6, 6, 18)] + [InlineData("<887C><012C><4797><41D0><5464>", 4, 4, 11)] + public void GetIndexOfFirstInvalidUtf16Sequence_WithEightRandomCharsContainingUnpairedSurrogates(string unprocessedInput, int idxOfFirstInvalidChar, int expectedRuneCount, int expectedUtf8ByteCount) + { + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(unprocessedInput, idxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + } + + [Fact] + public void GetIndexOfFirstInvalidUtf16Sequence_WithInvalidSurrogateSequences() + { + // All ASCII + + char[] chars = Enumerable.Repeat('x', 128).ToArray(); + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, -1, expectedRuneCount: 128, expectedUtf8ByteCount: 128); + + // Throw a surrogate pair at the beginning + + chars[0] = '\uD800'; + chars[1] = '\uDFFF'; + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, -1, expectedRuneCount: 127, expectedUtf8ByteCount: 130); + + // Throw a surrogate pair near the end + + chars[124] = '\uD800'; + chars[125] = '\uDFFF'; + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, -1, expectedRuneCount: 126, expectedUtf8ByteCount: 132); + + // Throw a standalone surrogate code point at the *very* end + + chars[127] = '\uD800'; // high surrogate + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, 127, expectedRuneCount: 125, expectedUtf8ByteCount: 131); + + chars[127] = '\uDFFF'; // low surrogate + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, 127, expectedRuneCount: 125, expectedUtf8ByteCount: 131); + + // Make the final surrogate pair valid + + chars[126] = '\uD800'; // high surrogate + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, -1, expectedRuneCount: 125, expectedUtf8ByteCount: 134); + + // Throw an invalid surrogate sequence in the middle (straddles a vector boundary) + + chars[12] = '\u0080'; // 2-byte UTF-8 sequence + chars[13] = '\uD800'; // high surrogate + chars[14] = '\uD800'; // high surrogate + chars[15] = '\uDFFF'; // low surrogate + chars[16] = '\uDFFF'; // low surrogate + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, 13, expectedRuneCount: 12, expectedUtf8ByteCount: 16); + + // Correct the surrogate sequence we just added + + chars[14] = '\uDC00'; // low surrogate + chars[15] = '\uDBFF'; // high surrogate + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, -1, expectedRuneCount: 123, expectedUtf8ByteCount: 139); + + // Corrupt the surrogate pair that's split across a vector boundary + + chars[16] = 'x'; // ASCII char (remember.. chars[15] is a high surrogate char) + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(chars, 15, expectedRuneCount: 13, expectedUtf8ByteCount: 20); + } + + private static void GetIndexOfFirstInvalidUtf16Sequence_Test_Core(string unprocessedInput, int expectedIdxOfFirstInvalidChar, int expectedRuneCount, long expectedUtf8ByteCount) + { + char[] processedInput = ProcessInput(unprocessedInput).ToCharArray(); + + // Run the test normally + + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(processedInput, expectedIdxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + + // Put a bunch of ASCII data at the beginning (to test the call to ASCIIUtility at method entry) + + processedInput = Enumerable.Repeat('x', 128).Concat(processedInput).ToArray(); + + if (expectedIdxOfFirstInvalidChar >= 0) + { + expectedIdxOfFirstInvalidChar += 128; + } + expectedRuneCount += 128; + expectedUtf8ByteCount += 128; + + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(processedInput, expectedIdxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + + // Change the first few chars to a mixture of 2-byte and 3-byte UTF-8 sequences + // This makes sure the vectorized code paths can properly handle these. + + processedInput[0] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[1] = '\u0800'; // 3-byte UTF-8 sequence + processedInput[2] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[3] = '\u0800'; // 3-byte UTF-8 sequence + processedInput[4] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[5] = '\u0800'; // 3-byte UTF-8 sequence + processedInput[6] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[7] = '\u0800'; // 3-byte UTF-8 sequence + + expectedUtf8ByteCount += 12; + + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(processedInput, expectedIdxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + + // Throw some surrogate pairs into the mix to make sure they're also handled properly + // by the vectorized code paths. + + processedInput[8] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[9] = '\u0800'; // 3-byte UTF-8 sequence + processedInput[10] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[11] = '\u0800'; // 3-byte UTF-8 sequence + processedInput[12] = '\u0080'; // 2-byte UTF-8 sequence + processedInput[13] = '\uD800'; // high surrogate + processedInput[14] = '\uDC00'; // low surrogate + processedInput[15] = 'z'; // ASCII char + + expectedRuneCount--; + expectedUtf8ByteCount += 9; + + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(processedInput, expectedIdxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + + // Split the next surrogate pair across the vector boundary (so that we + // don't inadvertently treat this as a standalone surrogate sequence). + + processedInput[15] = '\uDBFF'; // high surrogate + processedInput[16] = '\uDFFF'; // low surrogate + + expectedRuneCount--; + expectedUtf8ByteCount += 2; + + GetIndexOfFirstInvalidUtf16Sequence_Test_Core(processedInput, expectedIdxOfFirstInvalidChar, expectedRuneCount, expectedUtf8ByteCount); + } + + private static unsafe void GetIndexOfFirstInvalidUtf16Sequence_Test_Core(char[] input, int expectedRetVal, int expectedRuneCount, long expectedUtf8ByteCount) + { + // Arrange + + using BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(input); + boundedMemory.MakeReadonly(); + + // Act + + int actualRetVal; + long actualUtf8CodeUnitCount; + int actualRuneCount; + + fixed (char* pInputBuffer = &MemoryMarshal.GetReference(boundedMemory.Span)) + { + char* pFirstInvalidChar = _getPointerToFirstInvalidCharFn.Value(pInputBuffer, input.Length, out long utf8CodeUnitCountAdjustment, out int scalarCountAdjustment); + + long ptrDiff = pFirstInvalidChar - pInputBuffer; + Assert.True((ulong)ptrDiff <= (uint)input.Length, "ptrDiff was outside expected range."); + + Assert.True(utf8CodeUnitCountAdjustment >= 0, "UTF-16 code unit count adjustment must be non-negative."); + Assert.True(scalarCountAdjustment <= 0, "Scalar count adjustment must be 0 or negative."); + + actualRetVal = (ptrDiff == input.Length) ? -1 : (int)ptrDiff; + + // The last two 'out' parameters are: + // a) The number to be added to the "chars processed" return value to come up with the total UTF-8 code unit count, and + // b) The number to be added to the "total UTF-16 code unit count" value to come up with the total scalar count. + + actualUtf8CodeUnitCount = ptrDiff + utf8CodeUnitCountAdjustment; + actualRuneCount = (int)ptrDiff + scalarCountAdjustment; + } + + // Assert + + Assert.Equal(expectedRetVal, actualRetVal); + Assert.Equal(expectedRuneCount, actualRuneCount); + Assert.Equal(actualUtf8CodeUnitCount, expectedUtf8ByteCount); + } + + private static Lazy CreateGetPointerToFirstInvalidCharFn() + { + return new Lazy(() => + { + Type utf16UtilityType = typeof(Utf8).Assembly.GetType("System.Text.Unicode.Utf16Utility"); + + if (utf16UtilityType is null) + { + throw new Exception("Couldn't find Utf16Utility type in System.Private.CoreLib."); + } + + MethodInfo methodInfo = utf16UtilityType.GetMethod("GetPointerToFirstInvalidChar", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + if (methodInfo is null) + { + throw new Exception("Couldn't find GetPointerToFirstInvalidChar method on Utf8Utility."); + } + + return (GetPointerToFirstInvalidCharDel)methodInfo.CreateDelegate(typeof(GetPointerToFirstInvalidCharDel)); + }); + } + + private static string ProcessInput(string input) + { + input = input.Replace("", "\u00E9", StringComparison.Ordinal); // U+00E9 LATIN SMALL LETTER E WITH ACUTE + input = input.Replace("", "\u20AC", StringComparison.Ordinal); // U+20AC EURO SIGN + input = input.Replace("", "\U0001F600", StringComparison.Ordinal); // U+1F600 GRINNING FACE + + // Replace with \uABCD. This allows us to flow potentially malformed + // UTF-16 strings without Xunit. (The unit testing framework gets angry when + // we try putting invalid UTF-16 data as inline test data.) + + int idx; + while ((idx = input.IndexOf('<')) >= 0) + { + input = input[..idx] + (char)ushort.Parse(input.Substring(idx + 1, 4), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture) + input[idx + 6..]; + } + + return input; + } + } +} diff --git a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToBytes.netcoreapp.cs b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToBytes.netcoreapp.cs index 18ceedc2f832..5432da089bdb 100644 --- a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToBytes.netcoreapp.cs +++ b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToBytes.netcoreapp.cs @@ -119,6 +119,34 @@ public void ToBytes_WithLargeValidBuffers(string utf16Input) expectedNumCharsRead: expectedNumCharsConsumed, expectedUtf8Transcoding: concatenatedUtf8); } + + // now throw lots of ASCII data at the beginning so that we exercise the vectorized code paths + + utf16Input = new string('x', 64) + utf16Input; + concatenatedUtf8 = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + ToBytes_Test_Core( + utf16Input: utf16Input, + destinationSize: concatenatedUtf8.Length, + replaceInvalidSequences: false, + isFinalChunk: true, + expectedOperationStatus: OperationStatus.Done, + expectedNumCharsRead: utf16Input.Length, + expectedUtf8Transcoding: concatenatedUtf8); + + // now throw some non-ASCII data at the beginning so that we *don't* exercise the vectorized code paths + + utf16Input = WOMAN_CARTWHEELING_MEDSKIN_UTF16 + utf16Input[64..]; + concatenatedUtf8 = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + ToBytes_Test_Core( + utf16Input: utf16Input, + destinationSize: concatenatedUtf8.Length, + replaceInvalidSequences: false, + isFinalChunk: true, + expectedOperationStatus: OperationStatus.Done, + expectedNumCharsRead: utf16Input.Length, + expectedUtf8Transcoding: concatenatedUtf8); } [Theory] @@ -162,6 +190,18 @@ public void ToBytes_WithInvalidSurrogates(string utf16Input, int expectedNumChar expectedOperationStatus: OperationStatus.InvalidData, expectedNumCharsRead: expectedNumCharsConsumed, expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); + + // Now try the tests again with a larger buffer. + // This ensures that running out of destination space wasn't the reason we failed. + + ToBytes_Test_Core( + utf16Input: utf16Input, + destinationSize: (expectedUtf8TranscodingHex.Length) / 2 + 16, + replaceInvalidSequences: false, + isFinalChunk: false, + expectedOperationStatus: OperationStatus.InvalidData, + expectedNumCharsRead: expectedNumCharsConsumed, + expectedUtf8Transcoding: DecodeHex(expectedUtf8TranscodingHex)); } [Theory] diff --git a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToChars.netcoreapp.cs b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToChars.netcoreapp.cs index 6dda95dffc10..cb3933891ce0 100644 --- a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToChars.netcoreapp.cs +++ b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.ToChars.netcoreapp.cs @@ -42,6 +42,18 @@ public void ToChars_WithSmallInvalidBuffers(string utf8HexInput, int expectedNum expectedOperationStatus: OperationStatus.InvalidData, expectedNumBytesRead: expectedNumBytesConsumed, expectedUtf16Transcoding: expectedUtf16Transcoding); + + // Now try the tests again with a larger buffer. + // This ensures that running out of destination space wasn't the reason we failed. + + ToChars_Test_Core( + utf8Input: DecodeHex(utf8HexInput), + destinationSize: expectedUtf16Transcoding.Length + 16, + replaceInvalidSequences: false, + isFinalChunk: false, + expectedOperationStatus: OperationStatus.InvalidData, + expectedNumBytesRead: expectedNumBytesConsumed, + expectedUtf16Transcoding: expectedUtf16Transcoding); } [Theory] @@ -74,6 +86,18 @@ public void ToChars_WithVariousIncompleteBuffers(string utf8HexInput, int expect expectedOperationStatus: OperationStatus.NeedMoreData, expectedNumBytesRead: expectedNumBytesConsumed, expectedUtf16Transcoding: expectedUtf16Transcoding); + + // Now try the tests again with a larger buffer. + // This ensures that running out of destination space wasn't the reason we failed. + + ToChars_Test_Core( + utf8Input: DecodeHex(utf8HexInput), + destinationSize: expectedUtf16Transcoding.Length + 16, + replaceInvalidSequences: false, + isFinalChunk: false, + expectedOperationStatus: OperationStatus.NeedMoreData, + expectedNumBytesRead: expectedNumBytesConsumed, + expectedUtf16Transcoding: expectedUtf16Transcoding); } [Theory] @@ -104,7 +128,7 @@ public void ToChars_WithVariousIncompleteBuffers(string utf8HexInput, int expect [InlineData(EURO_SYMBOL_UTF16 + EURO_SYMBOL_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16 + E_ACUTE_UTF16)] // 2x 3-byte sequences + 4x 2-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing [InlineData(GRINNING_FACE_UTF16 + GRINNING_FACE_UTF16)] // 2x 4-byte sequences, exercises 4-byte sequence processing [InlineData(GRINNING_FACE_UTF16 + "@AB")] // single 4-byte sequence + 3 ASCII bytes, exercises 4-byte sequence processing and draining logic - [InlineData("\U0001F938\U0001F3FD\u200D\u2640\uFE0F")] // U+1F938 U+1F3FD U+200D U+2640 U+FE0F WOMAN CARTWHEELING: MEDIUM SKIN TONE, exercising switching between multiple sequence lengths + [InlineData(WOMAN_CARTWHEELING_MEDSKIN_UTF16)] // exercises switching between multiple sequence lengths public void ToChars_ValidBuffers(string utf16Input) { // We're going to run the tests with destination buffer lengths ranging from 0 all the way @@ -162,6 +186,34 @@ public void ToChars_ValidBuffers(string utf16Input) expectedNumBytesRead: expectedNumBytesConsumed, expectedUtf16Transcoding: concatenatedUtf16); } + + // now throw lots of ASCII data at the beginning so that we exercise the vectorized code paths + + utf16Input = new string('x', 64) + utf16Input; + utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + ToChars_Test_Core( + utf8Input: utf8Input, + destinationSize: utf16Input.Length, + replaceInvalidSequences: false, + isFinalChunk: true, + expectedOperationStatus: OperationStatus.Done, + expectedNumBytesRead: utf8Input.Length, + expectedUtf16Transcoding: utf16Input); + + // now throw some non-ASCII data at the beginning so that we *don't* exercise the vectorized code paths + + utf16Input = WOMAN_CARTWHEELING_MEDSKIN_UTF16 + utf16Input[64..]; + utf8Input = utf16Input.EnumerateRunes().SelectMany(ToUtf8).ToArray(); + + ToChars_Test_Core( + utf8Input: utf8Input, + destinationSize: utf16Input.Length, + replaceInvalidSequences: false, + isFinalChunk: true, + expectedOperationStatus: OperationStatus.Done, + expectedNumBytesRead: utf8Input.Length, + expectedUtf16Transcoding: utf16Input); } [Theory] @@ -182,6 +234,7 @@ public void ToChars_ValidBuffers(string utf16Input) [InlineData("3031" + "E17F80" + EURO_SYMBOL_UTF8 + EURO_SYMBOL_UTF8, 2, "01")] // Improperly terminated 3-byte sequence at start of DWORD [InlineData("3031" + "E1C080" + EURO_SYMBOL_UTF8 + EURO_SYMBOL_UTF8, 2, "01")] // Improperly terminated 3-byte sequence at start of DWORD [InlineData("3031" + "EDA080" + EURO_SYMBOL_UTF8 + EURO_SYMBOL_UTF8, 2, "01")] // Surrogate 3-byte sequence at start of DWORD + [InlineData("3031" + "E69C88" + "E59B" + "E69C88", 5, "01\u6708")] // Incomplete 3-byte sequence surrounded by valid 3-byte sequences [InlineData("3031" + "F5808080", 2, "01")] // [ F5 ] is always invalid [InlineData("3031" + "F6808080", 2, "01")] // [ F6 ] is always invalid [InlineData("3031" + "F7808080", 2, "01")] // [ F7 ] is always invalid @@ -208,6 +261,18 @@ public void ToChars_WithLargeInvalidBuffers(string utf8HexInput, int expectedNum expectedOperationStatus: OperationStatus.InvalidData, expectedNumBytesRead: expectedNumBytesConsumed, expectedUtf16Transcoding: expectedUtf16Transcoding); + + // Now try the tests again with a larger buffer. + // This ensures that running out of destination space wasn't the reason we failed. + + ToChars_Test_Core( + utf8Input: DecodeHex(utf8HexInput), + destinationSize: expectedUtf16Transcoding.Length + 16, + replaceInvalidSequences: false, + isFinalChunk: false, + expectedOperationStatus: OperationStatus.InvalidData, + expectedNumBytesRead: expectedNumBytesConsumed, + expectedUtf16Transcoding: expectedUtf16Transcoding); } [Theory] diff --git a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.netcoreapp.cs b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.netcoreapp.cs index 087235a81b74..f57c769c3697 100644 --- a/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.netcoreapp.cs +++ b/src/System.Runtime/tests/System/Text/Unicode/Utf8Tests.netcoreapp.cs @@ -33,7 +33,9 @@ public partial class Utf8Tests private const string GRINNING_FACE_UTF8 = "F09F9880"; // U+1F600 GRINNING FACE, 4 bytes private const string GRINNING_FACE_UTF16 = "\U0001F600"; - + + private const string WOMAN_CARTWHEELING_MEDSKIN_UTF16 = "\U0001F938\U0001F3FD\u200D\u2640\uFE0F"; // U+1F938 U+1F3FD U+200D U+2640 U+FE0F WOMAN CARTWHEELING: MEDIUM SKIN TONE + // All valid scalars [ U+0000 .. U+D7FF ] and [ U+E000 .. U+10FFFF ]. private static readonly IEnumerable s_allValidScalars = Enumerable.Range(0x0000, 0xD800).Concat(Enumerable.Range(0xE000, 0x110000 - 0xE000)).Select(value => new Rune(value)); @@ -59,7 +61,7 @@ static Utf8Tests() * COMMON UTILITIES FOR UNIT TESTS */ - private static byte[] DecodeHex(ReadOnlySpan inputHex) + public static byte[] DecodeHex(ReadOnlySpan inputHex) { Assert.True(Regex.IsMatch(inputHex.ToString(), "^([0-9a-fA-F]{2})*$"), "Input must be an even number of hex characters."); @@ -74,7 +76,7 @@ private static byte[] DecodeHex(ReadOnlySpan inputHex) // !! IMPORTANT !! // Don't delete this implementation, as we use it as a reference to make sure the framework's // transcoding logic is correct. - private static byte[] ToUtf8(Rune rune) + public static byte[] ToUtf8(Rune rune) { Assert.True(Rune.IsValid(rune.Value), $"Rune with value U+{(uint)rune.Value:X4} is not well-formed."); diff --git a/src/System.Runtime/tests/System/Text/Unicode/Utf8UtilityTests.ValidateBytes.netcoreapp.cs b/src/System.Runtime/tests/System/Text/Unicode/Utf8UtilityTests.ValidateBytes.netcoreapp.cs new file mode 100644 index 000000000000..899faa86ce3d --- /dev/null +++ b/src/System.Runtime/tests/System/Text/Unicode/Utf8UtilityTests.ValidateBytes.netcoreapp.cs @@ -0,0 +1,417 @@ +// 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.Buffers; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Xunit; + +namespace System.Text.Unicode.Tests +{ + public partial class Utf8UtilityTests + { + private unsafe delegate byte* GetPointerToFirstInvalidByteDel(byte* pInputBuffer, int inputLength, out int utf16CodeUnitCountAdjustment, out int scalarCountAdjustment); + private static readonly Lazy _getPointerToFirstInvalidByteFn = CreateGetPointerToFirstInvalidByteFn(); + + private const string X = "58"; // U+0058 LATIN CAPITAL LETTER X, 1 byte + private const string Y = "59"; // U+0058 LATIN CAPITAL LETTER Y, 1 byte + private const string Z = "5A"; // U+0058 LATIN CAPITAL LETTER Z, 1 byte + private const string E_ACUTE = "C3A9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE, 2 bytes + private const string EURO_SYMBOL = "E282AC"; // U+20AC EURO SIGN, 3 bytes + private const string GRINNING_FACE = "F09F9880"; // U+1F600 GRINNING FACE, 4 bytes + + [Theory] + [InlineData("", 0, 0)] // empty string is OK + [InlineData(X, 1, 0)] + [InlineData(X + Y, 2, 0)] + [InlineData(X + Y + Z, 3, 0)] + [InlineData(E_ACUTE, 1, 0)] + [InlineData(X + E_ACUTE, 2, 0)] + [InlineData(E_ACUTE + X, 2, 0)] + [InlineData(EURO_SYMBOL, 1, 0)] + public void GetIndexOfFirstInvalidUtf8Sequence_WithSmallValidBuffers(string input, int expectedRuneCount, int expectedSurrogatePairCount) + { + // These test cases are for the "slow processing" code path at the end of GetIndexOfFirstInvalidUtf8Sequence, + // so inputs should be less than 4 bytes. + + Assert.InRange(input.Length, 0, 6); + + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(input, -1 /* expectedRetVal */, expectedRuneCount, expectedSurrogatePairCount); + } + + [Theory] + [InlineData("80", 0, 0, 0)] // sequence cannot begin with continuation character + [InlineData("8182", 0, 0, 0)] // sequence cannot begin with continuation character + [InlineData("838485", 0, 0, 0)] // sequence cannot begin with continuation character + [InlineData(X + "80", 1, 1, 0)] // sequence cannot begin with continuation character + [InlineData(X + "8182", 1, 1, 0)] // sequence cannot begin with continuation character + [InlineData("C0", 0, 0, 0)] // [ C0 ] is always invalid + [InlineData("C080", 0, 0, 0)] // [ C0 ] is always invalid + [InlineData("C08081", 0, 0, 0)] // [ C0 ] is always invalid + [InlineData(X + "C1", 1, 1, 0)] // [ C1 ] is always invalid + [InlineData(X + "C180", 1, 1, 0)] // [ C1 ] is always invalid + [InlineData("C2", 0, 0, 0)] // [ C2 ] is improperly terminated + [InlineData(X + "C27F", 1, 1, 0)] // [ C2 ] is improperly terminated + [InlineData(X + "E282", 1, 1, 0)] // [ E2 82 ] is improperly terminated + [InlineData("E2827F", 0, 0, 0)] // [ E2 82 ] is improperly terminated + [InlineData("E09F80", 0, 0, 0)] // [ E0 9F ... ] is overlong + [InlineData("E0C080", 0, 0, 0)] // [ E0 ] is improperly terminated + [InlineData("ED7F80", 0, 0, 0)] // [ ED ] is improperly terminated + [InlineData("EDA080", 0, 0, 0)] // [ ED A0 ... ] is surrogate + public void GetIndexOfFirstInvalidUtf8Sequence_WithSmallInvalidBuffers(string input, int expectedRetVal, int expectedRuneCount, int expectedSurrogatePairCount) + { + // These test cases are for the "slow processing" code path at the end of GetIndexOfFirstInvalidUtf8Sequence, + // so inputs should be less than 4 bytes. + + Assert.InRange(input.Length, 0, 6); + + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(input, expectedRetVal, expectedRuneCount, expectedSurrogatePairCount); + } + + [Theory] + [InlineData(E_ACUTE + "21222324" + "303132333435363738393A3B3C3D3E3F", 21, 0)] // Loop unrolling at end of buffer + [InlineData(E_ACUTE + "21222324" + "303132333435363738393A3B3C3D3E3F" + "3031323334353637" + E_ACUTE + "38393A3B3C3D3E3F", 38, 0)] // Loop unrolling interrupted by non-ASCII + [InlineData("212223" + E_ACUTE + "30313233", 8, 0)] // 3 ASCII bytes followed by non-ASCII + [InlineData("2122" + E_ACUTE + "30313233", 7, 0)] // 2 ASCII bytes followed by non-ASCII + [InlineData("21" + E_ACUTE + "30313233", 6, 0)] // 1 ASCII byte followed by non-ASCII + [InlineData(E_ACUTE + E_ACUTE + E_ACUTE + E_ACUTE, 4, 0)] // 4x 2-byte sequences, exercises optimization code path in 2-byte sequence processing + [InlineData(E_ACUTE + E_ACUTE + E_ACUTE + "5051", 5, 0)] // 3x 2-byte sequences + 2 ASCII bytes, exercises optimization code path in 2-byte sequence processing + [InlineData(E_ACUTE + "5051", 3, 0)] // single 2-byte sequence + 2 trailing ASCII bytes, exercises draining logic in 2-byte sequence processing + [InlineData(E_ACUTE + "50" + E_ACUTE + "304050", 6, 0)] // single 2-byte sequences + 1 trailing ASCII byte + 2-byte sequence, exercises draining logic in 2-byte sequence processing + [InlineData(EURO_SYMBOL + "20", 2, 0)] // single 3-byte sequence + 1 trailing ASCII byte, exercises draining logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL + "203040", 4, 0)] // single 3-byte sequence + 3 trailing ASCII byte, exercises draining logic and "running out of data" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL + EURO_SYMBOL + EURO_SYMBOL, 3, 0)] // 3x 3-byte sequences, exercises "stay within 3-byte loop" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL + EURO_SYMBOL + EURO_SYMBOL + EURO_SYMBOL, 4, 0)] // 4x 3-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL + EURO_SYMBOL + EURO_SYMBOL + E_ACUTE, 4, 0)] // 3x 3-byte sequences + single 2-byte sequence, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(EURO_SYMBOL + EURO_SYMBOL + E_ACUTE + E_ACUTE + E_ACUTE + E_ACUTE, 6, 0)] // 2x 3-byte sequences + 4x 2-byte sequences, exercises "consume multiple bytes at a time" logic in 3-byte sequence processing + [InlineData(GRINNING_FACE + GRINNING_FACE, 2, 2)] // 2x 4-byte sequences, exercises 4-byte sequence processing + [InlineData(GRINNING_FACE + "303132", 4, 1)] // single 4-byte sequence + 3 ASCII bytes, exercises 4-byte sequence processing and draining logic + [InlineData("F09FA4B8" + "F09F8FBD" + "E2808D" + "E29980" + "EFB88F", 5, 2)] // U+1F938 U+1F3FD U+200D U+2640 U+FE0F WOMAN CARTWHEELING: MEDIUM SKIN TONE, exercising switching between multiple sequence lengths + public void GetIndexOfFirstInvalidUtf8Sequence_WithLargeValidBuffers(string input, int expectedRuneCount, int expectedSurrogatePairCount) + { + // These test cases are for the "fast processing" code which is the main loop of GetIndexOfFirstInvalidUtf8Sequence, + // so inputs should be less >= 4 bytes. + + Assert.True(input.Length >= 8); + + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(input, -1 /* expectedRetVal */, expectedRuneCount, expectedSurrogatePairCount); + } + + [Theory] + [InlineData("3031" + "80" + "202122232425", 2, 2, 0)] // Continuation character at start of sequence should match no bitmask + [InlineData("3031" + "C080" + "2021222324", 2, 2, 0)] // Overlong 2-byte sequence at start of DWORD + [InlineData("3031" + "C180" + "2021222324", 2, 2, 0)] // Overlong 2-byte sequence at start of DWORD + [InlineData("C280" + "C180", 2, 1, 0)] // Overlong 2-byte sequence at end of DWORD + [InlineData("C27F" + "C280", 0, 0, 0)] // Improperly terminated 2-byte sequence at start of DWORD + [InlineData("C2C0" + "C280", 0, 0, 0)] // Improperly terminated 2-byte sequence at start of DWORD + [InlineData("C280" + "C27F", 2, 1, 0)] // Improperly terminated 2-byte sequence at end of DWORD + [InlineData("C280" + "C2C0", 2, 1, 0)] // Improperly terminated 2-byte sequence at end of DWORD + [InlineData("C280" + "C280" + "80203040", 4, 2, 0)] // Continuation character at start of sequence, within "stay in 2-byte processing" optimization + [InlineData("C280" + "C280" + "C180" + "C280", 4, 2, 0)] // Overlong 2-byte sequence at start of DWORD, within "stay in 2-byte processing" optimization + [InlineData("C280" + "C280" + "C280" + "C180", 6, 3, 0)] // Overlong 2-byte sequence at end of DWORD, within "stay in 2-byte processing" optimization + [InlineData("3031" + "E09F80" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Overlong 3-byte sequence at start of DWORD + [InlineData("3031" + "E07F80" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Improperly terminated 3-byte sequence at start of DWORD + [InlineData("3031" + "E0C080" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Improperly terminated 3-byte sequence at start of DWORD + [InlineData("3031" + "E17F80" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Improperly terminated 3-byte sequence at start of DWORD + [InlineData("3031" + "E1C080" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Improperly terminated 3-byte sequence at start of DWORD + [InlineData("3031" + "EDA080" + EURO_SYMBOL + EURO_SYMBOL, 2, 2, 0)] // Surrogate 3-byte sequence at start of DWORD + [InlineData("3031" + "E69C88" + "E59B" + "E69C88", 5, 3, 0)] // Incomplete 3-byte sequence surrounded by valid 3-byte sequences + [InlineData("3031" + "F5808080", 2, 2, 0)] // [ F5 ] is always invalid + [InlineData("3031" + "F6808080", 2, 2, 0)] // [ F6 ] is always invalid + [InlineData("3031" + "F7808080", 2, 2, 0)] // [ F7 ] is always invalid + [InlineData("3031" + "F8808080", 2, 2, 0)] // [ F8 ] is always invalid + [InlineData("3031" + "F9808080", 2, 2, 0)] // [ F9 ] is always invalid + [InlineData("3031" + "FA808080", 2, 2, 0)] // [ FA ] is always invalid + [InlineData("3031" + "FB808080", 2, 2, 0)] // [ FB ] is always invalid + [InlineData("3031" + "FC808080", 2, 2, 0)] // [ FC ] is always invalid + [InlineData("3031" + "FD808080", 2, 2, 0)] // [ FD ] is always invalid + [InlineData("3031" + "FE808080", 2, 2, 0)] // [ FE ] is always invalid + [InlineData("3031" + "FF808080", 2, 2, 0)] // [ FF ] is always invalid + public void GetIndexOfFirstInvalidUtf8Sequence_WithLargeInvalidBuffers(string input, int expectedRetVal, int expectedRuneCount, int expectedSurrogatePairCount) + { + // These test cases are for the "fast processing" code which is the main loop of GetIndexOfFirstInvalidUtf8Sequence, + // so inputs should be less >= 4 bytes. + + Assert.True(input.Length >= 8); + + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(input, expectedRetVal, expectedRuneCount, expectedSurrogatePairCount); + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithOverlongTwoByteSequences_ReturnsInvalid() + { + // [ C0 ] is never a valid byte, indicates overlong 2-byte sequence + // We'll test that [ C0 ] [ 00..FF ] is treated as invalid + + for (int i = 0; i < 256; i++) + { + AssertIsInvalidTwoByteSequence(new byte[] { 0xC0, (byte)i }); + } + + // [ C1 ] is never a valid byte, indicates overlong 2-byte sequence + // We'll test that [ C1 ] [ 00..FF ] is treated as invalid + + for (int i = 0; i < 256; i++) + { + AssertIsInvalidTwoByteSequence(new byte[] { 0xC1, (byte)i }); + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithImproperlyTerminatedTwoByteSequences_ReturnsInvalid() + { + // Test [ C2..DF ] [ 00..7F ] and [ C2..DF ] [ C0..FF ] + + for (int i = 0xC2; i < 0xDF; i++) + { + for (int j = 0; j < 0x80; j++) + { + AssertIsInvalidTwoByteSequence(new byte[] { (byte)i, (byte)j }); + } + for (int j = 0xC0; j < 0x100; j++) + { + AssertIsInvalidTwoByteSequence(new byte[] { (byte)i, (byte)j }); + } + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithOverlongThreeByteSequences_ReturnsInvalid() + { + // [ E0 ] [ 80..9F ] [ 80..BF ] is overlong 3-byte sequence + + for (int i = 0x00; i < 0xA0; i++) + { + AssertIsInvalidThreeByteSequence(new byte[] { 0xE0, (byte)i, 0x80 }); + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithSurrogateThreeByteSequences_ReturnsInvalid() + { + // [ ED ] [ A0..BF ] [ 80..BF ] is surrogate 3-byte sequence + + for (int i = 0xA0; i < 0x100; i++) + { + AssertIsInvalidThreeByteSequence(new byte[] { 0xED, (byte)i, 0x80 }); + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithImproperlyTerminatedThreeByteSequence_ReturnsInvalid() + { + // [ E0..EF ] [ 80..BF ] [ !(80..BF) ] is improperly terminated 3-byte sequence + + for (int i = 0xE0; i < 0xF0; i++) + { + for (int j = 0x00; j < 0x80; j++) + { + // Use both '9F' and 'A0' to make sure at least one isn't caught by overlong / surrogate checks + AssertIsInvalidThreeByteSequence(new byte[] { (byte)i, 0x9F, (byte)j }); + AssertIsInvalidThreeByteSequence(new byte[] { (byte)i, 0xA0, (byte)j }); + } + for (int j = 0xC0; j < 0x100; j++) + { + // Use both '9F' and 'A0' to make sure at least one isn't caught by overlong / surrogate checks + AssertIsInvalidThreeByteSequence(new byte[] { (byte)i, 0x9F, (byte)j }); + AssertIsInvalidThreeByteSequence(new byte[] { (byte)i, 0xA0, (byte)j }); + } + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithOverlongFourByteSequences_ReturnsInvalid() + { + // [ F0 ] [ 80..8F ] [ 80..BF ] [ 80..BF ] is overlong 4-byte sequence + + for (int i = 0x00; i < 0x90; i++) + { + AssertIsInvalidFourByteSequence(new byte[] { 0xF0, (byte)i, 0x80, 0x80 }); + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithOutOfRangeFourByteSequences_ReturnsInvalid() + { + // [ F4 ] [ 90..BF ] [ 80..BF ] [ 80..BF ] is out-of-range 4-byte sequence + + for (int i = 0x90; i < 0x100; i++) + { + AssertIsInvalidFourByteSequence(new byte[] { 0xF4, (byte)i, 0x80, 0x80 }); + } + } + + [Fact] + public void GetIndexOfFirstInvalidUtf8Sequence_WithInvalidFourByteSequence_ReturnsInvalid() + { + // [ F0..F4 ] [ !(80..BF) ] [ !(80..BF) ] [ !(80..BF) ] is improperly terminated 4-byte sequence + + for (int i = 0xF0; i < 0xF5; i++) + { + for (int j = 0x00; j < 0x80; j++) + { + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, (byte)j, 0x80, 0x80 }); + + // Use both '8F' and '90' to make sure at least one isn't caught by overlong / out-of-range checks + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0x9F, (byte)j, 0x80 }); + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0xA0, (byte)j, 0x80 }); + + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0x9F, 0x80, (byte)j }); + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0xA0, 0x80, (byte)j }); + } + for (int j = 0xC0; j < 0x100; j++) + { + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, (byte)j, 0x80, 0x80 }); + + // Use both '8F' and '90' to make sure at least one isn't caught by overlong / out-of-range checks + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0x9F, (byte)j, 0x80 }); + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0xA0, (byte)j, 0x80 }); + + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0x9F, 0x80, (byte)j }); + AssertIsInvalidFourByteSequence(new byte[] { (byte)i, 0xA0, 0x80, (byte)j }); + } + } + } + + private static void AssertIsInvalidTwoByteSequence(byte[] invalidSequence) + { + Assert.Equal(2, invalidSequence.Length); + + byte[] knownGoodBytes = Utf8Tests.DecodeHex(E_ACUTE); + + byte[] toTest = invalidSequence.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at start of first DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 0, 0, 0); + + toTest = knownGoodBytes.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at end of first DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 2, 1, 0); + + // Run the same tests but with extra data at the beginning so that we're inside one of + // the 2-byte processing "hot loop" code paths. + + toTest = knownGoodBytes.Concat(knownGoodBytes).Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at start of next DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 4, 2, 0); + + toTest = knownGoodBytes.Concat(knownGoodBytes).Concat(knownGoodBytes).Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at end of next DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 6, 3, 0); + } + + private static void AssertIsInvalidThreeByteSequence(byte[] invalidSequence) + { + Assert.Equal(3, invalidSequence.Length); + + byte[] knownGoodBytes = Utf8Tests.DecodeHex(EURO_SYMBOL); + + byte[] toTest = invalidSequence.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at start of first DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 0, 0, 0); + + // Run the same tests but with extra data at the beginning so that we're inside one of + // the 3-byte processing "hot loop" code paths. + + toTest = knownGoodBytes.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // straddling first and second DWORDs + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 3, 1, 0); + + toTest = knownGoodBytes.Concat(knownGoodBytes).Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // straddling second and third DWORDs + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 6, 2, 0); + + toTest = knownGoodBytes.Concat(knownGoodBytes).Concat(knownGoodBytes).Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); // at end of third DWORD + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 9, 3, 0); + } + + private static void AssertIsInvalidFourByteSequence(byte[] invalidSequence) + { + Assert.Equal(4, invalidSequence.Length); + + byte[] knownGoodBytes = Utf8Tests.DecodeHex(GRINNING_FACE); + + byte[] toTest = invalidSequence.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 0, 0, 0); + + toTest = knownGoodBytes.Concat(invalidSequence).Concat(knownGoodBytes).ToArray(); + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(toTest, 4, 1, 1); + } + + private static void GetIndexOfFirstInvalidUtf8Sequence_Test_Core(string inputHex, int expectedRetVal, int expectedRuneCount, int expectedSurrogatePairCount) + { + byte[] inputBytes = Utf8Tests.DecodeHex(inputHex); + + // Run the test normally + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(inputBytes, expectedRetVal, expectedRuneCount, expectedSurrogatePairCount); + + // Then run the test with a bunch of ASCII data at the beginning (to exercise the vectorized code paths) + inputBytes = Enumerable.Repeat((byte)'x', 128).Concat(inputBytes).ToArray(); + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(inputBytes, (expectedRetVal < 0) ? expectedRetVal : (expectedRetVal + 128), expectedRuneCount + 128, expectedSurrogatePairCount); + + // Then put a few more ASCII bytes at the beginning (to test that offsets are properly handled) + inputBytes = Enumerable.Repeat((byte)'x', 7).Concat(inputBytes).ToArray(); + GetIndexOfFirstInvalidUtf8Sequence_Test_Core(inputBytes, (expectedRetVal < 0) ? expectedRetVal : (expectedRetVal + 135), expectedRuneCount + 135, expectedSurrogatePairCount); + } + + private static unsafe void GetIndexOfFirstInvalidUtf8Sequence_Test_Core(byte[] input, int expectedRetVal, int expectedRuneCount, int expectedSurrogatePairCount) + { + // Arrange + + using BoundedMemory boundedMemory = BoundedMemory.AllocateFromExistingData(input); + boundedMemory.MakeReadonly(); + + // Act + + int actualRetVal; + int actualSurrogatePairCount; + int actualRuneCount; + + fixed (byte* pInputBuffer = &MemoryMarshal.GetReference(boundedMemory.Span)) + { + byte* pFirstInvalidByte = _getPointerToFirstInvalidByteFn.Value(pInputBuffer, input.Length, out int utf16CodeUnitCountAdjustment, out int scalarCountAdjustment); + + long ptrDiff = pFirstInvalidByte - pInputBuffer; + Assert.True((ulong)ptrDiff <= (uint)input.Length, "ptrDiff was outside expected range."); + + Assert.True(utf16CodeUnitCountAdjustment <= 0, "UTF-16 code unit count adjustment must be 0 or negative."); + Assert.True(scalarCountAdjustment <= 0, "Scalar count adjustment must be 0 or negative."); + + actualRetVal = (ptrDiff == input.Length) ? -1 : (int)ptrDiff; + + // The last two 'out' parameters are: + // a) The number to be added to the "bytes processed" return value to come up with the total UTF-16 code unit count, and + // b) The number to be added to the "total UTF-16 code unit count" value to come up with the total scalar count. + + int totalUtf16CodeUnitCount = (int)ptrDiff + utf16CodeUnitCountAdjustment; + actualRuneCount = totalUtf16CodeUnitCount + scalarCountAdjustment; + + // Surrogate pair count is number of UTF-16 code units less the number of scalars. + + actualSurrogatePairCount = totalUtf16CodeUnitCount - actualRuneCount; + } + + // Assert + + Assert.Equal(expectedRetVal, actualRetVal); + Assert.Equal(expectedRuneCount, actualRuneCount); + Assert.Equal(expectedSurrogatePairCount, actualSurrogatePairCount); + } + + private static Lazy CreateGetPointerToFirstInvalidByteFn() + { + return new Lazy(() => + { + Type utf8UtilityType = typeof(Utf8).Assembly.GetType("System.Text.Unicode.Utf8Utility"); + + if (utf8UtilityType is null) + { + throw new Exception("Couldn't find Utf8Utility type in System.Private.CoreLib."); + } + + MethodInfo methodInfo = utf8UtilityType.GetMethod("GetPointerToFirstInvalidByte", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + if (methodInfo is null) + { + throw new Exception("Couldn't find GetPointerToFirstInvalidByte method on Utf8Utility."); + } + + return (GetPointerToFirstInvalidByteDel)methodInfo.CreateDelegate(typeof(GetPointerToFirstInvalidByteDel)); + }); + } + } +} diff --git a/src/System.Runtime/tests/System/VersionTests.cs b/src/System.Runtime/tests/System/VersionTests.cs index ab8c571ec1ea..45ded98a9b24 100644 --- a/src/System.Runtime/tests/System/VersionTests.cs +++ b/src/System.Runtime/tests/System/VersionTests.cs @@ -92,61 +92,85 @@ public void Ctor_NegativeRevision_ThrowsArgumentOutOfRangeException() AssertExtensions.Throws("revision", () => new Version(0, 0, 0, -1)); } - public static IEnumerable CompareTo_TestData() + public static IEnumerable Comparison_TestData() { - yield return new object[] { new Version(1, 2), null, 1 }; - yield return new object[] { new Version(1, 2), new Version(1, 2), 0 }; - yield return new object[] { new Version(1, 2), new Version(1, 3), -1 }; - yield return new object[] { new Version(1, 2), new Version(1, 1), 1 }; - yield return new object[] { new Version(1, 2), new Version(2, 0), -1 }; - yield return new object[] { new Version(1, 2), new Version(1, 2, 1), -1 }; - yield return new object[] { new Version(1, 2), new Version(1, 2, 0, 1), -1 }; - yield return new object[] { new Version(1, 2), new Version(1, 0), 1 }; - yield return new object[] { new Version(1, 2), new Version(1, 0, 1), 1 }; - yield return new object[] { new Version(1, 2), new Version(1, 0, 0, 1), 1 }; - - yield return new object[] { new Version(3, 2, 1), new Version(2, 2, 1), 1 }; - yield return new object[] { new Version(3, 2, 1), new Version(3, 1, 1), 1 }; - yield return new object[] { new Version(3, 2, 1), new Version(3, 2, 0), 1 }; - - yield return new object[] { new Version(1, 2, 3, 4), new Version(1, 2, 3, 4), 0 }; - yield return new object[] { new Version(1, 2, 3, 4), new Version(1, 2, 3, 5), -1 }; - yield return new object[] { new Version(1, 2, 3, 4), new Version(1, 2, 3, 3), 1 }; - - yield return new object[] { new Version(1, 2, 3, 4), null, 1 }; + foreach (var input in new (Version v1, Version v2, int expectedSign)[] + { + (null, null, 0), + + (new Version(1, 2), null, 1), + (new Version(1, 2), new Version(1, 2), 0), + (new Version(1, 2), new Version(1, 3), -1), + (new Version(1, 2), new Version(1, 1), 1), + (new Version(1, 2), new Version(2, 0), -1), + (new Version(1, 2), new Version(1, 2, 1), -1), + (new Version(1, 2), new Version(1, 2, 0, 1), -1), + (new Version(1, 2), new Version(1, 0), 1), + (new Version(1, 2), new Version(1, 0, 1), 1), + (new Version(1, 2), new Version(1, 0, 0, 1), 1), + + (new Version(3, 2, 1), null, 1), + (new Version(3, 2, 1), new Version(2, 2, 1), 1), + (new Version(3, 2, 1), new Version(3, 1, 1), 1), + (new Version(3, 2, 1), new Version(3, 2, 0), 1), + + (new Version(1, 2, 3, 4), null, 1), + (new Version(1, 2, 3, 4), new Version(1, 2, 3, 4), 0), + (new Version(1, 2, 3, 4), new Version(1, 2, 3, 5), -1), + (new Version(1, 2, 3, 4), new Version(1, 2, 3, 3), 1) + }) + { + yield return new object[] { input.v1, input.v2, input.expectedSign }; + yield return new object[] { input.v2, input.v1, input.expectedSign * -1 }; + } } [Theory] - [MemberData(nameof(CompareTo_TestData))] - public void CompareTo_Other_ReturnsExpected(Version version1, object other, int expectedSign) + [MemberData(nameof(Comparison_TestData))] + public void CompareTo_ReturnsExpected(Version version1, Version version2, int expectedSign) { - if (version1 != null && other is Version version2) + Assert.Equal(expectedSign, Comparer.Default.Compare(version1, version2)); + if (version1 != null) { - if (expectedSign >= 0) - { - Assert.True(version1 >= version2); - Assert.False(version1 < version2); - } - if (expectedSign > 0) - { - Assert.True(version1 > version2); - Assert.False(version1 <= version2); - } - if (expectedSign <= 0) - { - Assert.True(version1 <= version2); - Assert.False(version1 > version2); - } - if (expectedSign < 0) - { - Assert.True(version1 < version2); - Assert.False(version1 >= version2); - } + Assert.Equal(expectedSign, Math.Sign(((IComparable)version1).CompareTo(version2))); + Assert.Equal(expectedSign, Math.Sign(version1.CompareTo((object)version2))); + Assert.Equal(expectedSign, Math.Sign(version1.CompareTo(version2))); } + } - IComparable comparable = version1; - Assert.Equal(expectedSign, Math.Sign(comparable.CompareTo(other))); - Assert.Equal(expectedSign, Math.Sign(version1.CompareTo(other))); + [ActiveIssue("https://github.com/dotnet/coreclr/pull/23898")] + [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "https://github.com/dotnet/coreclr/pull/23898")] + [Theory] + [MemberData(nameof(Comparison_TestData))] + public void ComparisonOperators_ReturnExpected(Version version1, Version version2, int expectedSign) + { + if (expectedSign < 0) + { + Assert.True(version1 < version2); + Assert.True(version1 <= version2); + Assert.False(version1 == version2); + Assert.False(version1 >= version2); + Assert.False(version1 > version2); + Assert.True(version1 != version2); + } + else if (expectedSign == 0) + { + Assert.False(version1 < version2); + Assert.True(version1 <= version2); + Assert.True(version1 == version2); + Assert.True(version1 >= version2); + Assert.False(version1 > version2); + Assert.False(version1 != version2); + } + else + { + Assert.False(version1 < version2); + Assert.False(version1 <= version2); + Assert.False(version1 == version2); + Assert.True(version1 >= version2); + Assert.True(version1 > version2); + Assert.True(version1 != version2); + } } [Theory] @@ -159,17 +183,6 @@ public void CompareTo_ObjectNotAVersion_ThrowsArgumentException(object other) AssertExtensions.Throws(null, () => ((IComparable)version).CompareTo(other)); } - [Fact] - public void Comparisons_NullArgument_ThrowsArgumentNullException() - { - Version nullVersion = null; - Version nonNullVersion = new Version(1, 2); - AssertExtensions.Throws("v1", () => nonNullVersion >= nullVersion); - AssertExtensions.Throws("v1", () => nonNullVersion > nullVersion); - AssertExtensions.Throws("v1", () => nullVersion < nonNullVersion); - AssertExtensions.Throws("v1", () => nullVersion <= nonNullVersion); - } - public static IEnumerable Equals_TestData() { yield return new object[] { new Version(2, 3), new Version(2, 3), true }; diff --git a/src/System.Security.AccessControl/src/System.Security.AccessControl.csproj b/src/System.Security.AccessControl/src/System.Security.AccessControl.csproj index d3a1b96d7e47..4985809405bd 100644 --- a/src/System.Security.AccessControl/src/System.Security.AccessControl.csproj +++ b/src/System.Security.AccessControl/src/System.Security.AccessControl.csproj @@ -24,11 +24,13 @@ - Common\System\NotImplemented.cs + + Microsoft\Win32\SafeHandles\SafeTokenHandle.cs + Common\Interop\Interop.Libraries.cs diff --git a/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs b/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs index 1b1c74226f01..0a80301ba6a2 100644 --- a/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs +++ b/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Decrypt.cs @@ -113,6 +113,9 @@ public static unsafe ContentInfo TryDecryptCore( return null; } + // Compat: Previous versions of the managed PAL encryptor would wrap the contents in an octet stream + // which is not correct and is incompatible with other CMS readers. To maintain compatibility with + // existing CMS that have the incorrect wrapping, we attempt to remove it. if (contentType == Oids.Pkcs7Data) { byte[] tmp = null; diff --git a/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs b/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs index d99e0dfe079e..21d35ce2d16c 100644 --- a/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs +++ b/src/System.Security.Cryptography.Pkcs/src/Internal/Cryptography/Pal/AnyOS/ManagedPal.Encrypt.cs @@ -182,7 +182,6 @@ private byte[] EncryptContent( if (contentInfo.ContentType.Value == Oids.Pkcs7Data) { - toEncrypt = EncodeOctetString(toEncrypt); return encryptor.OneShot(toEncrypt); } else diff --git a/src/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/GeneralTests.cs b/src/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/GeneralTests.cs index 8b7b6169410e..56b1215d8d21 100644 --- a/src/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/GeneralTests.cs +++ b/src/System.Security.Cryptography.Pkcs/tests/EnvelopedCms/GeneralTests.cs @@ -286,6 +286,58 @@ public static void RoundTrip_ExplicitSki() Assert.Equal(contentInfo.Content, ecms.ContentInfo.Content); } + [Fact] + public static void Encrypt_Data_DoesNotIncreaseInSize() + { + byte[] content = new byte[15]; // One short of AES block size boundary + ContentInfo contentInfo = new ContentInfo(content); + AlgorithmIdentifier identifier = new AlgorithmIdentifier(new Oid(Oids.Aes128)); + EnvelopedCms ecms = new EnvelopedCms(contentInfo, identifier); + + using (X509Certificate2 cert = Certificates.RSAKeyTransfer1.GetCertificate()) + { + CmsRecipient recipient = new CmsRecipient(cert); + ecms.Encrypt(recipient); + } + + byte[] encoded = ecms.Encode(); + EnvelopedCms reDecoded = new EnvelopedCms(); + reDecoded.Decode(encoded); + int expectedSize = PlatformDetection.IsFullFramework ? 22 : 16; //NetFx compat. + Assert.Equal(expectedSize, reDecoded.ContentInfo.Content.Length); + } + + [Fact] + [OuterLoop(/* Leaks key on disk if interrupted */)] + [PlatformSpecific(~TestPlatforms.Windows)] /* Applies to managed PAL only. */ + public static void FromManagedPal_CompatWithOctetStringWrappedContents_Decrypt() + { + byte[] expectedContent = new byte[] { 1, 2, 3 }; + byte[] encodedMessage = + ("3082010C06092A864886F70D010703A081FE3081FB0201003181C83081C5020100302" + + "E301A311830160603550403130F5253414B65795472616E7366657231021031D935FB" + + "63E8CFAB48A0BF7B397B67C0300D06092A864886F70D0101010500048180586BCA530" + + "9A74A211859714715D90B8E13A7712838746877DF7D68B0BCF36DE3F77854276C8EAD" + + "389ADD8402697E4FFF215143E0E63676349592CB3A86FF556230D5F4AC4A9A6758219" + + "9E65281A8B63DFBCFB7180E6B54C6E38BECAF09624C6B6D2B3058F280FE8F0BF8EBA3" + + "57AECC1B9B177E98671A9659B034501AE3D58789302B06092A864886F70D010701301" + + "406082A864886F70D0307040810B222648FDC0DE38008036BB59C8B6A784B").HexToByteArray(); + EnvelopedCms ecms = new EnvelopedCms(); + ecms.Decode(encodedMessage); + + using (X509Certificate2 privateCert = Certificates.RSAKeyTransfer1.TryGetCertificateWithPrivateKey()) + { + if (privateCert == null) + { + return; //Private key not available. + } + + ecms.Decrypt(new X509Certificate2Collection(privateCert)); + } + + Assert.Equal(expectedContent, ecms.ContentInfo.Content); + } + private static X509Certificate2[] s_certs = { Certificates.RSAKeyTransfer1.GetCertificate(), diff --git a/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj b/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj index 12db6d8d2c11..aa37d9c3089f 100644 --- a/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj +++ b/src/System.Security.Cryptography.X509Certificates/tests/System.Security.Cryptography.X509Certificates.Tests.csproj @@ -128,6 +128,6 @@ - + diff --git a/src/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeSecurityHandles.cs b/src/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeSecurityHandles.cs deleted file mode 100644 index 3db37143d65c..000000000000 --- a/src/System.Security.Principal.Windows/src/Microsoft/Win32/SafeHandles/SafeSecurityHandles.cs +++ /dev/null @@ -1,59 +0,0 @@ -// 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.Runtime.InteropServices; -using System.Security; - -namespace Microsoft.Win32.SafeHandles -{ - internal sealed class SafeLsaMemoryHandle : SafeBuffer - { - private SafeLsaMemoryHandle() : base(true) { } - - // 0 is an Invalid Handle - internal SafeLsaMemoryHandle(IntPtr handle) : base(true) - { - SetHandle(handle); - } - - override protected bool ReleaseHandle() - { - return Interop.Advapi32.LsaFreeMemory(handle) == 0; - } - } - - internal sealed class SafeLsaPolicyHandle : SafeHandleZeroOrMinusOneIsInvalid - { - private SafeLsaPolicyHandle() : base(true) { } - - // 0 is an Invalid Handle - internal SafeLsaPolicyHandle(IntPtr handle) : base(true) - { - SetHandle(handle); - } - - override protected bool ReleaseHandle() - { - return Interop.Advapi32.LsaClose(handle) == 0; - } - } - - internal sealed class SafeLsaReturnBufferHandle : SafeBuffer - { - private SafeLsaReturnBufferHandle() : base(true) { } - - // 0 is an Invalid Handle - internal SafeLsaReturnBufferHandle(IntPtr handle) : base(true) - { - SetHandle(handle); - } - - override protected bool ReleaseHandle() - { - // LsaFreeReturnBuffer returns an NTSTATUS - return Interop.SspiCli.LsaFreeReturnBuffer(handle) >= 0; - } - } -} diff --git a/src/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj b/src/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj index 9bc6a0bf66eb..690f503615a6 100644 --- a/src/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj +++ b/src/System.Security.Principal.Windows/src/System.Security.Principal.Windows.csproj @@ -10,7 +10,6 @@ - @@ -23,6 +22,12 @@ Common\Interop\Interop.Libraries.cs + + Common\Interop\Windows\Advapi32\Interop.UNICODE_STRING.cs + + + Common\Interop\Windows\Advapi32\Interop.OBJECT_ATTRIBUTES.cs + Common\Interop\Interop.TOKENS.cs @@ -41,9 +46,6 @@ Common\Interop\Interop.SECURITY_LOGON_SESSION_DATA.cs - - Common\Interop\Interop.UNICODE_STRING.cs - Common\Interop\Interop.GetCurrentProcess.cs @@ -116,6 +118,9 @@ Common\Interop\Interop.LsaNtStatusToWinError.cs + + Common\Interop\Windows\Advapi32\Interop.LSA_STRING.cs + Common\Interop\Interop.LocalFree.cs @@ -146,12 +151,6 @@ Common\Interop\Windows\SspiCli\Interop.LsaLookupAuthenticationPackage.cs - - Common\Interop\Windows\SspiCli\Interop.LsaString.cs - - - Common\Interop\Windows\SspiCli\Interop.LsaUnicodeString.cs - Common\Interop\Windows\SspiCli\Interop.QuotaLimits.cs @@ -170,6 +169,15 @@ Common\Microsoft\Win32\SafeHandles\SafeLsaHandle.cs + + Common\Microsoft\Win32\SafeHandles\SafeLsaMemoryHandle.cs + + + Common\Microsoft\Win32\SafeHandles\SafeLsaPolicyHandle.cs + + + Common\Microsoft\Win32\SafeHandles\SafeLsaReturnBufferHandle.cs + diff --git a/src/System.Security.Principal.Windows/src/System/Security/Principal/NTAccount.cs b/src/System.Security.Principal.Windows/src/System/Security/Principal/NTAccount.cs index 8ccf20b7ed56..c14e184a281e 100644 --- a/src/System.Security.Principal.Windows/src/System/Security/Principal/NTAccount.cs +++ b/src/System.Security.Principal.Windows/src/System/Security/Principal/NTAccount.cs @@ -247,7 +247,7 @@ private static IdentityReferenceCollection TranslateToSids(IdentityReferenceColl // Construct an array of unicode strings // - Interop.UNICODE_STRING[] Names = new Interop.UNICODE_STRING[sourceAccounts.Count]; + Interop.Advapi32.MARSHALLED_UNICODE_STRING[] Names = new Interop.Advapi32.MARSHALLED_UNICODE_STRING[sourceAccounts.Count]; int currentName = 0; foreach (IdentityReference id in sourceAccounts) diff --git a/src/System.Security.Principal.Windows/src/System/Security/Principal/Win32.cs b/src/System.Security.Principal.Windows/src/System/Security/Principal/Win32.cs index 4e151b9a67b7..524a3e75827e 100644 --- a/src/System.Security.Principal.Windows/src/System/Security/Principal/Win32.cs +++ b/src/System.Security.Principal.Windows/src/System/Security/Principal/Win32.cs @@ -40,33 +40,26 @@ internal static SafeLsaPolicyHandle LsaOpenPolicy( string systemName, PolicyRights rights) { - uint ReturnCode; - SafeLsaPolicyHandle Result; - Interop.LSA_OBJECT_ATTRIBUTES Loa; - - Loa.Length = Marshal.SizeOf(); - Loa.RootDirectory = IntPtr.Zero; - Loa.ObjectName = IntPtr.Zero; - Loa.Attributes = 0; - Loa.SecurityDescriptor = IntPtr.Zero; - Loa.SecurityQualityOfService = IntPtr.Zero; - - if (0 == (ReturnCode = Interop.Advapi32.LsaOpenPolicy(systemName, ref Loa, (int)rights, out Result))) + SafeLsaPolicyHandle policyHandle; + + var attributes = new Interop.OBJECT_ATTRIBUTES(); + uint error = Interop.Advapi32.LsaOpenPolicy(systemName, ref attributes, (int)rights, out policyHandle); + if (error == 0) { - return Result; + return policyHandle; } - else if (ReturnCode == Interop.StatusOptions.STATUS_ACCESS_DENIED) + else if (error == Interop.StatusOptions.STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } - else if (ReturnCode == Interop.StatusOptions.STATUS_INSUFFICIENT_RESOURCES || - ReturnCode == Interop.StatusOptions.STATUS_NO_MEMORY) + else if (error == Interop.StatusOptions.STATUS_INSUFFICIENT_RESOURCES || + error == Interop.StatusOptions.STATUS_NO_MEMORY) { throw new OutOfMemoryException(); } else { - uint win32ErrorCode = Interop.Advapi32.LsaNtStatusToWinError(ReturnCode); + uint win32ErrorCode = Interop.Advapi32.LsaNtStatusToWinError(error); throw new Win32Exception(unchecked((int)win32ErrorCode)); } diff --git a/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs b/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs index fa9f46609743..aef5d4934d10 100644 --- a/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs +++ b/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs @@ -16,7 +16,7 @@ using KERB_S4U_LOGON = Interop.SspiCli.KERB_S4U_LOGON; using KerbS4uLogonFlags = Interop.SspiCli.KerbS4uLogonFlags; using LUID = Interop.LUID; -using LSA_STRING = Interop.SspiCli.LSA_STRING; +using LSA_STRING = Interop.Advapi32.LSA_STRING; using QUOTA_LIMITS = Interop.SspiCli.QUOTA_LIMITS; using SECURITY_LOGON_TYPE = Interop.SspiCli.SECURITY_LOGON_TYPE; using TOKEN_SOURCE = Interop.SspiCli.TOKEN_SOURCE; diff --git a/src/System.Text.Encoding.Extensions/src/Configurations.props b/src/System.Text.Encoding.Extensions/src/Configurations.props index 3d283d470086..1bb5366a0750 100644 --- a/src/System.Text.Encoding.Extensions/src/Configurations.props +++ b/src/System.Text.Encoding.Extensions/src/Configurations.props @@ -2,9 +2,8 @@ uap-Windows_NT; + uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Text.Encoding.Extensions/src/System.Text.Encoding.Extensions.csproj b/src/System.Text.Encoding.Extensions/src/System.Text.Encoding.Extensions.csproj index 973d9cd796e4..066bab06c9e0 100644 --- a/src/System.Text.Encoding.Extensions/src/System.Text.Encoding.Extensions.csproj +++ b/src/System.Text.Encoding.Extensions/src/System.Text.Encoding.Extensions.csproj @@ -3,7 +3,7 @@ System.Text.Encoding.Extensions true {72BFA60D-4F91-4F84-AC6A-910B587DA1BF} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release diff --git a/src/System.Text.Encoding/src/Configurations.props b/src/System.Text.Encoding/src/Configurations.props index 06e87b8a189c..eb8b2ba06c34 100644 --- a/src/System.Text.Encoding/src/Configurations.props +++ b/src/System.Text.Encoding/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Text.Encoding/src/System.Text.Encoding.csproj b/src/System.Text.Encoding/src/System.Text.Encoding.csproj index fbae515c79b2..5803f637c103 100644 --- a/src/System.Text.Encoding/src/System.Text.Encoding.csproj +++ b/src/System.Text.Encoding/src/System.Text.Encoding.csproj @@ -3,7 +3,7 @@ System.Text.Encoding true {635F30B9-5566-4096-B772-68FAA9B00DF4} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Text.Encoding/tests/NegativeEncodingTests.cs b/src/System.Text.Encoding/tests/NegativeEncodingTests.cs index 2718f6f5287c..e5ae72d3cf40 100644 --- a/src/System.Text.Encoding/tests/NegativeEncodingTests.cs +++ b/src/System.Text.Encoding/tests/NegativeEncodingTests.cs @@ -45,7 +45,14 @@ public static IEnumerable Encodings_TestData() public static unsafe void GetByteCount_Invalid(Encoding encoding) { // Chars is null - AssertExtensions.Throws(encoding is ASCIIEncoding ? "chars" : "s", () => encoding.GetByteCount((string)null)); + if (PlatformDetection.IsNetCore) + { + AssertExtensions.Throws((encoding is ASCIIEncoding || encoding is UTF8Encoding) ? "chars" : "s", () => encoding.GetByteCount((string)null)); + } + else + { + AssertExtensions.Throws((encoding is ASCIIEncoding) ? "chars" : "s", () => encoding.GetByteCount((string)null)); + } AssertExtensions.Throws("chars", () => encoding.GetByteCount((char[])null)); AssertExtensions.Throws("chars", () => encoding.GetByteCount((char[])null, 0, 0)); diff --git a/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingDecode.cs b/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingDecode.cs index 618858cbc559..03577e7fffaa 100644 --- a/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingDecode.cs +++ b/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingDecode.cs @@ -111,7 +111,7 @@ public void Decode(byte[] bytes, int index, int count, string expected) EncodingHelpers.Decode(new UTF8Encoding(false, true), bytes, index, count, expected); EncodingHelpers.Decode(new UTF8Encoding(true, true), bytes, index, count, expected); } - + public static IEnumerable Decode_InvalidBytes_TestData() { yield return new object[] { new byte[] { 196, 84, 101, 115, 116, 196, 196, 196, 176, 176, 84, 101, 115, 116, 176 }, 0, 15, "\uFFFDTest\uFFFD\uFFFD\u0130\uFFFDTest\uFFFD" }; @@ -126,97 +126,217 @@ public static IEnumerable Decode_InvalidBytes_TestData() yield return new object[] { validSurrogateBytes, 2, 2, "\uFFFD\uFFFD" }; yield return new object[] { validSurrogateBytes, 2, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xA0, 0x80 }, 0, 3, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xAF, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xB0, 0x80 }, 0, 3, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xBF, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; - - // Invalid surrogate pair (low/low, high/high, low/high) - yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xAF, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xB0, 0x80, 0xED, 0xB0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xA0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; - - // Too high scalar value in surrogates - yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xEE, 0x80, 0x80 }, 0, 6, "\uFFFD\uFFFD\uE000" }; - yield return new object[] { new byte[] { 0xF4, 0x90, 0x80, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; - - // These are examples of overlong sequences. This can cause security - // vulnerabilities (e.g. MS00-078) so it is important we parse these as invalid. - yield return new object[] { new byte[] { 0xC0 }, 0, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 0xC0, 0xAF }, 0, 2, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xE0, 0x80, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF0, 0x80, 0x80, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF8, 0x80, 0x80, 0x80, 0xBF }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xFC, 0x80, 0x80, 0x80, 0x80, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xC0, 0xBF }, 0, 2, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xE0, 0x9C, 0x90 }, 0, 3, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF0, 0x8F, 0xA4, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xEF, 0x41 }, 0, 2, "\uFFFD\u0041" }; - yield return new object[] { new byte[] { 0xEF, 0xBF, 0xAE }, 0, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 0xEF, 0xBF, 0x41 }, 0, 3, "\uFFFD\u0041" }; - yield return new object[] { new byte[] { 0xEF, 0xBF, 0x61 }, 0, 3, "\uFFFD\u0061" }; - yield return new object[] { new byte[] { 0xEF, 0xBF, 0xEF, 0xBF, 0xAE }, 0, 5, "\uFFFD\uFFEE" }; - yield return new object[] { new byte[] { 0xEF, 0xBF, 0xC0, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xF0, 0xC4, 0x80 }, 0, 3, "\uFFFD\u0100" }; - - yield return new object[] { new byte[] { 176 }, 0, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 196 }, 0, 1, "\uFFFD" }; - - yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0x52, 0x7C, 0x7B, 0x41, 0x6E, 0x47, 0x65, 0xA3, 0xA4 }, 0, 12, "\uFFFD\uFFFD\u0061\u0052\u007C\u007B\u0041\u006E\u0047\u0065\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xA3 }, 0, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 0xA3, 0xA4 }, 0, 2, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0x65, 0xA3, 0xA4 }, 0, 3, "\u0065\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0x47, 0x65, 0xA3, 0xA4 }, 0, 4, "\u0047\u0065\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3, 0xA4 }, 0, 5, "\uFFFD\uFFFD\u0061\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3 }, 0, 4, "\uFFFD\uFFFD\u0061\uFFFD" }; - yield return new object[] { new byte[] { 0xD0, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; - yield return new object[] { new byte[] { 0xA4, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; - yield return new object[] { new byte[] { 0xD0, 0x61, 0x52, 0xA3 }, 0, 4, "\uFFFD\u0061\u0052\uFFFD" }; - - yield return new object[] { new byte[] { 0xAA }, 0, 1, "\uFFFD" }; - yield return new object[] { new byte[] { 0xAA, 0x41 }, 0, 2, "\uFFFD\u0041" }; - - yield return new object[] { new byte[] { 0xEF, 0xFF, 0xEE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xEF, 0xFF, 0xAE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 15, "\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F }, 0, 15, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F" }; - - yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 8, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xC2, 0xDF }, 0, 2, "\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0x80, 0x80, 0xC1, 0x80, 0xC1, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0x7F, 0x7F, 0x7F, 0x7F, 0xC3, 0xA1, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 14, "\uFFFD\u007F\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u00E1\uFFFD\u007F\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0xE0, 0xBF, 0x7F, 0xE0, 0xBF, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xE0, 0x9F, 0x80, 0xE0, 0xC0, 0x80, 0xE0, 0x9F, 0xBF, 0xE0, 0xC0, 0xBF }, 0, 12, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0x7F, 0xE0, 0xBF, 0x7F, 0xC3, 0xA1, 0xE0, 0xBF, 0xC0 }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\u007F\uFFFD\u007F\u00E1\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xE1, 0x80, 0x7F, 0xE1, 0x80, 0xC0, 0xE1, 0xBF, 0x7F, 0xE1, 0xBF, 0xC0, 0xEC, 0x80, 0x7F, 0xEC, 0x80, 0xC0, 0xEC, 0xBF, 0x7F, 0xEC, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xE1, 0x7F, 0x80, 0xE1, 0xC0, 0x80, 0xE1, 0x7F, 0xBF, 0xE1, 0xC0, 0xBF, 0xEC, 0x7F, 0x80, 0xEC, 0xC0, 0x80, 0xEC, 0x7F, 0xBF, 0xEC, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xED, 0x80, 0x7F, 0xED, 0x80, 0xC0, 0xED, 0x9F, 0x7F, 0xED, 0x9F, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xE8, 0x80, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u8000\uFFFD\u007F\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xEE, 0x80, 0x7F, 0xEE, 0x80, 0xC0, 0xEE, 0xBF, 0x7F, 0xEE, 0xBF, 0xC0, 0xEF, 0x80, 0x7F, 0xEF, 0x80, 0xC0, 0xEF, 0xBF, 0x7F, 0xEF, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xEE, 0x7F, 0x80, 0xEE, 0xC0, 0x80, 0xEE, 0x7F, 0xBF, 0xEE, 0xC0, 0xBF, 0xEF, 0x7F, 0x80, 0xEF, 0xC0, 0x80, 0xEF, 0x7F, 0xBF, 0xEF, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xF0, 0x90, 0x80, 0x7F, 0xF0, 0x90, 0x80, 0xC0, 0xF0, 0xBF, 0xBF, 0x7F, 0xF0, 0xBF, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF0, 0x90, 0x7F, 0x80, 0xF0, 0x90, 0xC0, 0x80, 0xF0, 0x90, 0x7F, 0xBF, 0xF0, 0x90, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF0, 0x8F, 0x80, 0x80, 0xF0, 0xC0, 0x80, 0x80, 0xF0, 0x8F, 0xBF, 0xBF, 0xF0, 0xC0, 0xBF, 0xBF }, 0, 16, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xF1, 0x80, 0x80, 0x7F, 0xF1, 0x80, 0x80, 0xC0, 0xF1, 0xBF, 0xBF, 0x7F, 0xF1, 0xBF, 0xBF, 0xC0, 0xF3, 0x80, 0x80, 0x7F, 0xF3, 0x80, 0x80, 0xC0, 0xF3, 0xBF, 0xBF, 0x7F, 0xF3, 0xBF, 0xBF, 0xC0 }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF1, 0x80, 0x7F, 0x80, 0xF1, 0x80, 0xC0, 0x80, 0xF1, 0x80, 0x7F, 0xBF, 0xF1, 0x80, 0xC0, 0xBF, 0xF3, 0x80, 0x7F, 0x80, 0xF3, 0x80, 0xC0, 0x80, 0xF3, 0x80, 0x7F, 0xBF, 0xF3, 0x80, 0xC0, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF1, 0x7F, 0x80, 0x80, 0xF1, 0xC0, 0x80, 0x80, 0xF1, 0x7F, 0xBF, 0xBF, 0xF1, 0xC0, 0xBF, 0xBF, 0xF3, 0x7F, 0x80, 0x80, 0xF3, 0xC0, 0x80, 0x80, 0xF3, 0x7F, 0xBF, 0xBF, 0xF3, 0xC0, 0xBF, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; - - yield return new object[] { new byte[] { 0xF4, 0x80, 0x80, 0x7F, 0xF4, 0x80, 0x80, 0xC0, 0xF4, 0x8F, 0xBF, 0x7F, 0xF4, 0x8F, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF4, 0x80, 0x7F, 0x80, 0xF4, 0x80, 0xC0, 0x80, 0xF4, 0x80, 0x7F, 0xBF, 0xF4, 0x80, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; - yield return new object[] { new byte[] { 0xF4, 0x7F, 0x80, 0x80, 0xF4, 0x90, 0x80, 0x80, 0xF4, 0x7F, 0xBF, 0xBF, 0xF4, 0x90, 0xBF, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + if (PlatformDetection.IsNetCore) + { + // Overlong 2-byte sequences + yield return new object[] { new byte[] { 0xC0, 0x80 }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xC1, 0x80 }, 0, 2, "\uFFFD\uFFFD" }; + + // Incomplete 2-byte sequences + yield return new object[] { new byte[] { 0xC2, 0x41 }, 0, 2, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xC2, 0x41 }, 0, 2, "\uFFFD\u0041" }; + + // Overlong 3-byte sequences + yield return new object[] { new byte[] { 0xE0, 0x80, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x9F, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + + // Truncated 3-byte sequences + yield return new object[] { new byte[] { 0xE0, 0xA0, 0x41 }, 0, 3, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xED, 0x9F, 0x41 }, 0, 3, "\uFFFD\u0041" }; + + // UTF-16 surrogate code points (invalid to be encoded in UTF-8) + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xAF, 0xBF }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xB0, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xBF, 0xBF }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xAF, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xB0, 0x80, 0xED, 0xB0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xA0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + // Overlong 4-byte sequences + yield return new object[] { new byte[] { 0xF0, 0x80, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x8F, 0x80 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + + // Truncated 4-byte sequences + yield return new object[] { new byte[] { 0xF0, 0x90, 0x41, 0x42 }, 0, 4, "\uFFFD\u0041\u0042" }; + yield return new object[] { new byte[] { 0xF0, 0x90, 0x80, 0x42 }, 0, 4, "\uFFFD\u0042" }; + + // Too high scalar value in surrogates + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xEE, 0x80, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uE000" }; + yield return new object[] { new byte[] { 0xF4, 0x90, 0x80, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + + // More examples of overlong sequences. This can cause security + // vulnerabilities (e.g. MS00-078) so it is important we parse these as invalid. + yield return new object[] { new byte[] { 0xC0 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xC0, 0xAF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x80, 0xBF }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x80, 0x80, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF8, 0x80, 0x80, 0x80, 0xBF }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xFC, 0x80, 0x80, 0x80, 0x80, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xC0, 0xBF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x9C, 0x90 }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x8F, 0xA4, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xEF, 0x41 }, 0, 2, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xAE }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0x41 }, 0, 3, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0x61 }, 0, 3, "\uFFFD\u0061" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xEF, 0xBF, 0xAE }, 0, 5, "\uFFFD\uFFEE" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xC0, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF0, 0xC4, 0x80 }, 0, 3, "\uFFFD\u0100" }; + + yield return new object[] { new byte[] { 176 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 196 }, 0, 1, "\uFFFD" }; + + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0x52, 0x7C, 0x7B, 0x41, 0x6E, 0x47, 0x65, 0xA3, 0xA4 }, 0, 12, "\uFFFD\uFFFD\u0061\u0052\u007C\u007B\u0041\u006E\u0047\u0065\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xA3 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xA3, 0xA4 }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x65, 0xA3, 0xA4 }, 0, 3, "\u0065\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x47, 0x65, 0xA3, 0xA4 }, 0, 4, "\u0047\u0065\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3, 0xA4 }, 0, 5, "\uFFFD\uFFFD\u0061\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3 }, 0, 4, "\uFFFD\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xD0, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xD0, 0x61, 0x52, 0xA3 }, 0, 4, "\uFFFD\u0061\u0052\uFFFD" }; + + yield return new object[] { new byte[] { 0xAA }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xAA, 0x41 }, 0, 2, "\uFFFD\u0041" }; + + yield return new object[] { new byte[] { 0xEF, 0xFF, 0xEE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xEF, 0xFF, 0xAE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 15, "\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F }, 0, 15, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F" }; + + yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 8, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xC2, 0xDF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x80, 0x80, 0xC1, 0x80, 0xC1, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0x7F, 0x7F, 0x7F, 0x7F, 0xC3, 0xA1, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 14, "\uFFFD\u007F\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u00E1\uFFFD\u007F\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0xE0, 0xBF, 0x7F, 0xE0, 0xBF, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x9F, 0x80, 0xE0, 0xC0, 0x80, 0xE0, 0x9F, 0xBF, 0xE0, 0xC0, 0xBF }, 0, 12, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0x7F, 0xE0, 0xBF, 0x7F, 0xC3, 0xA1, 0xE0, 0xBF, 0xC0 }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\u007F\uFFFD\u007F\u00E1\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xE1, 0x80, 0x7F, 0xE1, 0x80, 0xC0, 0xE1, 0xBF, 0x7F, 0xE1, 0xBF, 0xC0, 0xEC, 0x80, 0x7F, 0xEC, 0x80, 0xC0, 0xEC, 0xBF, 0x7F, 0xEC, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE1, 0x7F, 0x80, 0xE1, 0xC0, 0x80, 0xE1, 0x7F, 0xBF, 0xE1, 0xC0, 0xBF, 0xEC, 0x7F, 0x80, 0xEC, 0xC0, 0x80, 0xEC, 0x7F, 0xBF, 0xEC, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xED, 0x80, 0x7F, 0xED, 0x80, 0xC0, 0xED, 0x9F, 0x7F, 0xED, 0x9F, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xE8, 0x80, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\u8000\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xEE, 0x80, 0x7F, 0xEE, 0x80, 0xC0, 0xEE, 0xBF, 0x7F, 0xEE, 0xBF, 0xC0, 0xEF, 0x80, 0x7F, 0xEF, 0x80, 0xC0, 0xEF, 0xBF, 0x7F, 0xEF, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xEE, 0x7F, 0x80, 0xEE, 0xC0, 0x80, 0xEE, 0x7F, 0xBF, 0xEE, 0xC0, 0xBF, 0xEF, 0x7F, 0x80, 0xEF, 0xC0, 0x80, 0xEF, 0x7F, 0xBF, 0xEF, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF0, 0x90, 0x80, 0x7F, 0xF0, 0x90, 0x80, 0xC0, 0xF0, 0xBF, 0xBF, 0x7F, 0xF0, 0xBF, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x90, 0x7F, 0x80, 0xF0, 0x90, 0xC0, 0x80, 0xF0, 0x90, 0x7F, 0xBF, 0xF0, 0x90, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x8F, 0x80, 0x80, 0xF0, 0xC0, 0x80, 0x80, 0xF0, 0x8F, 0xBF, 0xBF, 0xF0, 0xC0, 0xBF, 0xBF }, 0, 16, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF1, 0x80, 0x80, 0x7F, 0xF1, 0x80, 0x80, 0xC0, 0xF1, 0xBF, 0xBF, 0x7F, 0xF1, 0xBF, 0xBF, 0xC0, 0xF3, 0x80, 0x80, 0x7F, 0xF3, 0x80, 0x80, 0xC0, 0xF3, 0xBF, 0xBF, 0x7F, 0xF3, 0xBF, 0xBF, 0xC0 }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF1, 0x80, 0x7F, 0x80, 0xF1, 0x80, 0xC0, 0x80, 0xF1, 0x80, 0x7F, 0xBF, 0xF1, 0x80, 0xC0, 0xBF, 0xF3, 0x80, 0x7F, 0x80, 0xF3, 0x80, 0xC0, 0x80, 0xF3, 0x80, 0x7F, 0xBF, 0xF3, 0x80, 0xC0, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF1, 0x7F, 0x80, 0x80, 0xF1, 0xC0, 0x80, 0x80, 0xF1, 0x7F, 0xBF, 0xBF, 0xF1, 0xC0, 0xBF, 0xBF, 0xF3, 0x7F, 0x80, 0x80, 0xF3, 0xC0, 0x80, 0x80, 0xF3, 0x7F, 0xBF, 0xBF, 0xF3, 0xC0, 0xBF, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF4, 0x80, 0x80, 0x7F, 0xF4, 0x80, 0x80, 0xC0, 0xF4, 0x8F, 0xBF, 0x7F, 0xF4, 0x8F, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF4, 0x80, 0x7F, 0x80, 0xF4, 0x80, 0xC0, 0x80, 0xF4, 0x80, 0x7F, 0xBF, 0xF4, 0x80, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF4, 0x7F, 0x80, 0x80, 0xF4, 0x90, 0x80, 0x80, 0xF4, 0x7F, 0xBF, 0xBF, 0xF4, 0x90, 0xBF, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + } + else + { + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80 }, 0, 3, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xAF, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xB0, 0x80 }, 0, 3, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xBF, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; + + // Invalid surrogate pair (low/low, high/high, low/high) + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xAF, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xB0, 0x80, 0xED, 0xB0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xED, 0xA0, 0x80 }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD" }; + + // Too high scalar value in surrogates + yield return new object[] { new byte[] { 0xED, 0xA0, 0x80, 0xEE, 0x80, 0x80 }, 0, 6, "\uFFFD\uFFFD\uE000" }; + yield return new object[] { new byte[] { 0xF4, 0x90, 0x80, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; + + // These are examples of overlong sequences. This can cause security + // vulnerabilities (e.g. MS00-078) so it is important we parse these as invalid. + yield return new object[] { new byte[] { 0xC0 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xC0, 0xAF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x80, 0xBF }, 0, 3, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x80, 0x80, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF8, 0x80, 0x80, 0x80, 0xBF }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xFC, 0x80, 0x80, 0x80, 0x80, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xC0, 0xBF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x9C, 0x90 }, 0, 3, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x8F, 0xA4, 0x80 }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xEF, 0x41 }, 0, 2, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xAE }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0x41 }, 0, 3, "\uFFFD\u0041" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0x61 }, 0, 3, "\uFFFD\u0061" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xEF, 0xBF, 0xAE }, 0, 5, "\uFFFD\uFFEE" }; + yield return new object[] { new byte[] { 0xEF, 0xBF, 0xC0, 0xBF }, 0, 4, "\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF0, 0xC4, 0x80 }, 0, 3, "\uFFFD\u0100" }; + + yield return new object[] { new byte[] { 176 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 196 }, 0, 1, "\uFFFD" }; + + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0x52, 0x7C, 0x7B, 0x41, 0x6E, 0x47, 0x65, 0xA3, 0xA4 }, 0, 12, "\uFFFD\uFFFD\u0061\u0052\u007C\u007B\u0041\u006E\u0047\u0065\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xA3 }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xA3, 0xA4 }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x65, 0xA3, 0xA4 }, 0, 3, "\u0065\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x47, 0x65, 0xA3, 0xA4 }, 0, 4, "\u0047\u0065\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3, 0xA4 }, 0, 5, "\uFFFD\uFFFD\u0061\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0xD0, 0x61, 0xA3 }, 0, 4, "\uFFFD\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xD0, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xA4, 0x61, 0xA3 }, 0, 3, "\uFFFD\u0061\uFFFD" }; + yield return new object[] { new byte[] { 0xD0, 0x61, 0x52, 0xA3 }, 0, 4, "\uFFFD\u0061\u0052\uFFFD" }; + + yield return new object[] { new byte[] { 0xAA }, 0, 1, "\uFFFD" }; + yield return new object[] { new byte[] { 0xAA, 0x41 }, 0, 2, "\uFFFD\u0041" }; + + yield return new object[] { new byte[] { 0xEF, 0xFF, 0xEE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xEF, 0xFF, 0xAE }, 0, 3, "\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 5, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x90, 0xA0, 0xB0, 0xC1 }, 0, 15, "\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x80, 0x90, 0xA0, 0xB0, 0xC1, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F }, 0, 15, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F\u007F" }; + + yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 8, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xC2, 0xDF }, 0, 2, "\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0x80, 0x80, 0xC1, 0x80, 0xC1, 0xBF }, 0, 6, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xC2, 0x7F, 0xC2, 0xC0, 0x7F, 0x7F, 0x7F, 0x7F, 0xC3, 0xA1, 0xDF, 0x7F, 0xDF, 0xC0 }, 0, 14, "\uFFFD\u007F\uFFFD\uFFFD\u007F\u007F\u007F\u007F\u00E1\uFFFD\u007F\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0xE0, 0xBF, 0x7F, 0xE0, 0xBF, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0x9F, 0x80, 0xE0, 0xC0, 0x80, 0xE0, 0x9F, 0xBF, 0xE0, 0xC0, 0xBF }, 0, 12, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE0, 0xA0, 0x7F, 0xE0, 0xA0, 0xC0, 0x7F, 0xE0, 0xBF, 0x7F, 0xC3, 0xA1, 0xE0, 0xBF, 0xC0 }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\u007F\uFFFD\u007F\u00E1\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xE1, 0x80, 0x7F, 0xE1, 0x80, 0xC0, 0xE1, 0xBF, 0x7F, 0xE1, 0xBF, 0xC0, 0xEC, 0x80, 0x7F, 0xEC, 0x80, 0xC0, 0xEC, 0xBF, 0x7F, 0xEC, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xE1, 0x7F, 0x80, 0xE1, 0xC0, 0x80, 0xE1, 0x7F, 0xBF, 0xE1, 0xC0, 0xBF, 0xEC, 0x7F, 0x80, 0xEC, 0xC0, 0x80, 0xEC, 0x7F, 0xBF, 0xEC, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xED, 0x80, 0x7F, 0xED, 0x80, 0xC0, 0xED, 0x9F, 0x7F, 0xED, 0x9F, 0xC0 }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 12, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xED, 0x7F, 0x80, 0xED, 0xA0, 0x80, 0xE8, 0x80, 0x80, 0xED, 0x7F, 0xBF, 0xED, 0xA0, 0xBF }, 0, 15, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u8000\uFFFD\u007F\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xEE, 0x80, 0x7F, 0xEE, 0x80, 0xC0, 0xEE, 0xBF, 0x7F, 0xEE, 0xBF, 0xC0, 0xEF, 0x80, 0x7F, 0xEF, 0x80, 0xC0, 0xEF, 0xBF, 0x7F, 0xEF, 0xBF, 0xC0 }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xEE, 0x7F, 0x80, 0xEE, 0xC0, 0x80, 0xEE, 0x7F, 0xBF, 0xEE, 0xC0, 0xBF, 0xEF, 0x7F, 0x80, 0xEF, 0xC0, 0x80, 0xEF, 0x7F, 0xBF, 0xEF, 0xC0, 0xBF }, 0, 24, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF0, 0x90, 0x80, 0x7F, 0xF0, 0x90, 0x80, 0xC0, 0xF0, 0xBF, 0xBF, 0x7F, 0xF0, 0xBF, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x90, 0x7F, 0x80, 0xF0, 0x90, 0xC0, 0x80, 0xF0, 0x90, 0x7F, 0xBF, 0xF0, 0x90, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF0, 0x8F, 0x80, 0x80, 0xF0, 0xC0, 0x80, 0x80, 0xF0, 0x8F, 0xBF, 0xBF, 0xF0, 0xC0, 0xBF, 0xBF }, 0, 16, "\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF1, 0x80, 0x80, 0x7F, 0xF1, 0x80, 0x80, 0xC0, 0xF1, 0xBF, 0xBF, 0x7F, 0xF1, 0xBF, 0xBF, 0xC0, 0xF3, 0x80, 0x80, 0x7F, 0xF3, 0x80, 0x80, 0xC0, 0xF3, 0xBF, 0xBF, 0x7F, 0xF3, 0xBF, 0xBF, 0xC0 }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF1, 0x80, 0x7F, 0x80, 0xF1, 0x80, 0xC0, 0x80, 0xF1, 0x80, 0x7F, 0xBF, 0xF1, 0x80, 0xC0, 0xBF, 0xF3, 0x80, 0x7F, 0x80, 0xF3, 0x80, 0xC0, 0x80, 0xF3, 0x80, 0x7F, 0xBF, 0xF3, 0x80, 0xC0, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF1, 0x7F, 0x80, 0x80, 0xF1, 0xC0, 0x80, 0x80, 0xF1, 0x7F, 0xBF, 0xBF, 0xF1, 0xC0, 0xBF, 0xBF, 0xF3, 0x7F, 0x80, 0x80, 0xF3, 0xC0, 0x80, 0x80, 0xF3, 0x7F, 0xBF, 0xBF, 0xF3, 0xC0, 0xBF, 0xBF }, 0, 32, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + + yield return new object[] { new byte[] { 0xF4, 0x80, 0x80, 0x7F, 0xF4, 0x80, 0x80, 0xC0, 0xF4, 0x8F, 0xBF, 0x7F, 0xF4, 0x8F, 0xBF, 0xC0 }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF4, 0x80, 0x7F, 0x80, 0xF4, 0x80, 0xC0, 0x80, 0xF4, 0x80, 0x7F, 0xBF, 0xF4, 0x80, 0xC0, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD" }; + yield return new object[] { new byte[] { 0xF4, 0x7F, 0x80, 0x80, 0xF4, 0x90, 0x80, 0x80, 0xF4, 0x7F, 0xBF, 0xBF, 0xF4, 0x90, 0xBF, 0xBF }, 0, 16, "\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u007F\uFFFD\uFFFD\uFFFD\uFFFD\uFFFD" }; + } } [Theory] @@ -229,5 +349,32 @@ public static void Decode_InvalidBytes(byte[] bytes, int index, int count, strin NegativeEncodingTests.Decode_Invalid(new UTF8Encoding(false, true), bytes, index, count); NegativeEncodingTests.Decode_Invalid(new UTF8Encoding(true, true), bytes, index, count); } + + [Theory] + [InlineData("", "ABCDEF")] + [InlineData("\uFFFD", "\uFFFDAB\uFFFDCD\uFFFDEF\uFFFD")] + [InlineData("?", "?AB?CD?EF?")] + [InlineData("\uFFFD?", "\uFFFD?AB\uFFFD?CD\uFFFD?EF\uFFFD?")] + public void Decode_InvalidChars_WithCustomReplacementFallback(string replacementString, string expected) + { + byte[] utf8Input = new byte[] + { + 0xC0, // always an invalid byte + (byte)'A', (byte)'B', + 0xF4, 0x80, 0xBF, // incomplete 4-byte sequence + (byte)'C', (byte)'D', + 0xE0, // incomplete 3-byte sequence + (byte)'E', (byte)'F', + 0xC2, // incomplete 2-byte sequence + }; + + Encoding utf8Encoding = Encoding.GetEncoding( + name: "utf-8", + encoderFallback: EncoderFallback.ExceptionFallback, + decoderFallback: new DecoderReplacementFallback(replacementString)); + + string actualUtf16Output = utf8Encoding.GetString(utf8Input); // pass in an invalid UTF-8 sequence + Assert.Equal(expected, actualUtf16Output); + } } } diff --git a/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingEncode.cs b/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingEncode.cs index e2e7bbc793a2..b15f4d10ea35 100644 --- a/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingEncode.cs +++ b/src/System.Text.Encoding/tests/UTF8Encoding/UTF8EncodingEncode.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; using Xunit; namespace System.Text.Tests @@ -248,5 +249,24 @@ public void Encode_InvalidChars(string chars, int index, int count, byte[] expec new UTF8Encoding(encoderShouldEmitUTF8Identifier: true, throwOnInvalidBytes: true), chars, index, count); } + + [Theory] + [InlineData("", "ABCDEF")] + [InlineData("\uFFFD", "\uFFFDAB\uFFFDCD\uFFFDEF\uFFFD")] + [InlineData("?", "?AB?CD?EF?")] + [InlineData("\uFFFD?", "\uFFFD?AB\uFFFD?CD\uFFFD?EF\uFFFD?")] + public void Encode_InvalidChars_WithCustomReplacementFallback(string replacementString, string expected) + { + byte[] expectedUtf8Output = expected.SelectMany(ch => (ch == '\uFFFD') ? new byte[] { 0xEF, 0xBF, 0xBD } : new byte[] { (byte)ch }).ToArray(); + + Encoding utf8Encoding = Encoding.GetEncoding( + name: "utf-8", + encoderFallback: new EncoderReplacementFallback(replacementString), + decoderFallback: DecoderFallback.ExceptionFallback); + + byte[] actualUtf8Output = utf8Encoding.GetBytes("\uD800AB\uDC00CD\uDFFFEF\uDBFF"); // pass in an invalid UTF-16 sequence + + Assert.Equal(expectedUtf8Output, actualUtf8Output); + } } } diff --git a/src/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj b/src/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj index e2ba61fb2108..40d70c61523e 100644 --- a/src/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj +++ b/src/System.Text.Json/pkg/Microsoft.Bcl.Json.Sources.pkgproj @@ -14,7 +14,7 @@ the Common SR.cs into SourcePackageFiles. --> - <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" /> + <_ProjectsToBuild Include="../src/System.Text.Json.csproj" UndefineProperties="Configuration" AdditionalProperties="TargetGroup=netstandard"/> utf8PropertyName, ref Utf8JsonWriter writer) { } - public void WriteAsProperty(ReadOnlySpan propertyName, ref Utf8JsonWriter writer) { } - public void WriteAsValue(ref System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsProperty(System.ReadOnlySpan utf8PropertyName, System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsProperty(System.ReadOnlySpan propertyName, System.Text.Json.Utf8JsonWriter writer) { } + public void WriteAsValue(System.Text.Json.Utf8JsonWriter writer) { } public partial struct ArrayEnumerator : System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerator, System.Collections.IEnumerable, System.Collections.IEnumerator, System.IDisposable { private object _dummy; @@ -117,6 +118,7 @@ public override void GetObjectData(System.Runtime.Serialization.SerializationInf public partial struct JsonReaderOptions { private int _dummyPrimitive; + public bool AllowTrailingCommas { get { throw null; } set { } } public System.Text.Json.JsonCommentHandling CommentHandling { get { throw null; } set { } } public int MaxDepth { get { throw null; } set { } } } @@ -161,15 +163,6 @@ public partial struct JsonWriterOptions public bool Indented { get { throw null; } set { } } public bool SkipValidation { get { throw null; } set { } } } - public partial struct JsonWriterState - { - private object _dummy; - private int _dummyPrimitive; - public JsonWriterState(System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { throw null; } - public long BytesCommitted { get { throw null; } } - public long BytesWritten { get { throw null; } } - public System.Text.Json.JsonWriterOptions Options { get { throw null; } } - } public ref partial struct Utf8JsonReader { private object _dummy; @@ -214,56 +207,60 @@ public ref partial struct Utf8JsonReader [System.CLSCompliantAttribute(false)] public bool TryGetUInt64(out ulong value) { throw null; } } - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter : System.IDisposable { - private object _dummy; - private int _dummyPrimitive; - public Utf8JsonWriter(System.Buffers.IBufferWriter bufferWriter, System.Text.Json.JsonWriterState state = default(System.Text.Json.JsonWriterState)) { throw null; } + public Utf8JsonWriter(System.Buffers.IBufferWriter bufferWriter, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { } + public Utf8JsonWriter(System.IO.Stream utf8Json, System.Text.Json.JsonWriterOptions options = default(System.Text.Json.JsonWriterOptions)) { } public long BytesCommitted { get { throw null; } } - public long BytesWritten { get { throw null; } } + public int BytesPending { get { throw null; } } public int CurrentDepth { get { throw null; } } - public void Flush(bool isFinalBlock = true) { } - public System.Text.Json.JsonWriterState GetCurrentState() { throw null; } - public void WriteBoolean(System.ReadOnlySpan utf8PropertyName, bool value, bool escape = true) { } - public void WriteBoolean(System.ReadOnlySpan propertyName, bool value, bool escape = true) { } - public void WriteBoolean(string propertyName, bool value, bool escape = true) { } + public System.Text.Json.JsonWriterOptions Options { get { throw null; } } + public void Dispose() { } + public void Flush() { } + public System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public void Reset() { } + public void Reset(System.Buffers.IBufferWriter bufferWriter) { } + public void Reset(System.IO.Stream utf8Json) { } + public void WriteBoolean(System.ReadOnlySpan utf8PropertyName, bool value) { } + public void WriteBoolean(System.ReadOnlySpan propertyName, bool value) { } + public void WriteBoolean(string propertyName, bool value) { } public void WriteBooleanValue(bool value) { } - public void WriteCommentValue(System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteCommentValue(System.ReadOnlySpan value, bool escape = true) { } - public void WriteCommentValue(string value, bool escape = true) { } + public void WriteCommentValue(System.ReadOnlySpan utf8Value) { } + public void WriteCommentValue(System.ReadOnlySpan value) { } + public void WriteCommentValue(string value) { } public void WriteEndArray() { } public void WriteEndObject() { } - public void WriteNull(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteNull(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteNull(string propertyName, bool escape = true) { } + public void WriteNull(System.ReadOnlySpan utf8PropertyName) { } + public void WriteNull(System.ReadOnlySpan propertyName) { } + public void WriteNull(string propertyName) { } public void WriteNullValue() { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, decimal value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, double value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, int value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, long value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, decimal value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, double value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, int value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, long value) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, uint value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan utf8PropertyName, ulong value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, decimal value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, double value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, int value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, long value, bool escape = true) { } - public void WriteNumber(System.ReadOnlySpan propertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan utf8PropertyName, ulong value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, decimal value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, double value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, int value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, long value) { } + public void WriteNumber(System.ReadOnlySpan propertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan propertyName, uint value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan propertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(System.ReadOnlySpan propertyName, ulong value, bool escape = true) { } - public void WriteNumber(string propertyName, decimal value, bool escape = true) { } - public void WriteNumber(string propertyName, double value, bool escape = true) { } - public void WriteNumber(string propertyName, int value, bool escape = true) { } - public void WriteNumber(string propertyName, long value, bool escape = true) { } - public void WriteNumber(string propertyName, float value, bool escape = true) { } + public void WriteNumber(System.ReadOnlySpan propertyName, ulong value) { } + public void WriteNumber(string propertyName, decimal value) { } + public void WriteNumber(string propertyName, double value) { } + public void WriteNumber(string propertyName, int value) { } + public void WriteNumber(string propertyName, long value) { } + public void WriteNumber(string propertyName, float value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(string propertyName, uint value, bool escape = true) { } + public void WriteNumber(string propertyName, uint value) { } [System.CLSCompliantAttribute(false)] - public void WriteNumber(string propertyName, ulong value, bool escape = true) { } + public void WriteNumber(string propertyName, ulong value) { } public void WriteNumberValue(decimal value) { } public void WriteNumberValue(double value) { } public void WriteNumberValue(int value) { } @@ -274,41 +271,62 @@ public void WriteNumberValue(uint value) { } [System.CLSCompliantAttribute(false)] public void WriteNumberValue(ulong value) { } public void WriteStartArray() { } - public void WriteStartArray(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteStartArray(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteStartArray(string propertyName, bool escape = true) { } + public void WriteStartArray(System.ReadOnlySpan utf8PropertyName) { } + public void WriteStartArray(System.ReadOnlySpan propertyName) { } + public void WriteStartArray(string propertyName) { } public void WriteStartObject() { } - public void WriteStartObject(System.ReadOnlySpan utf8PropertyName, bool escape = true) { } - public void WriteStartObject(System.ReadOnlySpan propertyName, bool escape = true) { } - public void WriteStartObject(string propertyName, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTime value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.Guid value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan utf8PropertyName, string value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.DateTime value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.Guid value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(System.ReadOnlySpan propertyName, string value, bool escape = true) { } - public void WriteString(string propertyName, System.DateTime value, bool escape = true) { } - public void WriteString(string propertyName, System.DateTimeOffset value, bool escape = true) { } - public void WriteString(string propertyName, System.Guid value, bool escape = true) { } - public void WriteString(string propertyName, System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteString(string propertyName, System.ReadOnlySpan value, bool escape = true) { } - public void WriteString(string propertyName, string value, bool escape = true) { } + public void WriteStartObject(System.ReadOnlySpan utf8PropertyName) { } + public void WriteStartObject(System.ReadOnlySpan propertyName) { } + public void WriteStartObject(string propertyName) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTime value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.DateTimeOffset value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.Guid value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, System.ReadOnlySpan value) { } + public void WriteString(System.ReadOnlySpan utf8PropertyName, string value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.DateTime value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.DateTimeOffset value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.Guid value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(System.ReadOnlySpan propertyName, System.ReadOnlySpan value) { } + public void WriteString(System.ReadOnlySpan propertyName, string value) { } + public void WriteString(string propertyName, System.DateTime value) { } + public void WriteString(string propertyName, System.DateTimeOffset value) { } + public void WriteString(string propertyName, System.Guid value) { } + public void WriteString(string propertyName, System.ReadOnlySpan utf8Value) { } + public void WriteString(string propertyName, System.ReadOnlySpan value) { } + public void WriteString(string propertyName, string value) { } public void WriteStringValue(System.DateTime value) { } public void WriteStringValue(System.DateTimeOffset value) { } public void WriteStringValue(System.Guid value) { } - public void WriteStringValue(System.ReadOnlySpan utf8Value, bool escape = true) { } - public void WriteStringValue(System.ReadOnlySpan value, bool escape = true) { } - public void WriteStringValue(string value, bool escape = true) { } + public void WriteStringValue(System.ReadOnlySpan utf8Value) { } + public void WriteStringValue(System.ReadOnlySpan value) { } + public void WriteStringValue(string value) { } } } namespace System.Text.Json.Serialization { + public abstract partial class JsonAttribute : System.Attribute + { + protected JsonAttribute() { } + } + [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed partial class JsonIgnoreAttribute : System.Text.Json.Serialization.JsonAttribute + { + public JsonIgnoreAttribute() { } + } + public abstract partial class JsonNamingPolicy + { + protected JsonNamingPolicy() { } + public static System.Text.Json.Serialization.JsonNamingPolicy CamelCase { get { throw null; } } + public abstract string ConvertName(string name); + } + [System.AttributeUsageAttribute(System.AttributeTargets.Property, AllowMultiple=false)] + public sealed partial class JsonPropertyNameAttribute : System.Text.Json.Serialization.JsonAttribute + { + public JsonPropertyNameAttribute(string propertyName) { } + public string Name { get { throw null; } set { } } + } public static partial class JsonSerializer { public static object Parse(System.ReadOnlySpan utf8Json, System.Type returnType, System.Text.Json.Serialization.JsonSerializerOptions options = null) { throw null; } @@ -327,10 +345,15 @@ public static partial class JsonSerializer public sealed partial class JsonSerializerOptions { public JsonSerializerOptions() { } + public bool AllowTrailingCommas { get { throw null; } set { } } public int DefaultBufferSize { get { throw null; } set { } } - public bool IgnoreNullPropertyValueOnRead { get { throw null; } set { } } - public bool IgnoreNullPropertyValueOnWrite { get { throw null; } set { } } - public System.Text.Json.JsonReaderOptions ReaderOptions { get { throw null; } set { } } - public System.Text.Json.JsonWriterOptions WriterOptions { get { throw null; } set { } } + public System.Text.Json.Serialization.JsonNamingPolicy DictionaryKeyPolicy { get { throw null; } set { } } + public bool IgnoreNullValues { get { throw null; } set { } } + public bool IgnoreReadOnlyProperties { get { throw null; } set { } } + public int MaxDepth { get { throw null; } set { } } + public bool PropertyNameCaseInsensitive { get { throw null; } set { } } + public System.Text.Json.Serialization.JsonNamingPolicy PropertyNamingPolicy { get { throw null; } set { } } + public System.Text.Json.JsonCommentHandling ReadCommentHandling { get { throw null; } set { } } + public bool WriteIndented { get { throw null; } set { } } } } diff --git a/src/System.Text.Json/ref/System.Text.Json.csproj b/src/System.Text.Json/ref/System.Text.Json.csproj index 002d0f8d6487..0914d098732b 100644 --- a/src/System.Text.Json/ref/System.Text.Json.csproj +++ b/src/System.Text.Json/ref/System.Text.Json.csproj @@ -7,6 +7,7 @@ + diff --git a/src/System.Text.Json/src/Resources/Strings.resx b/src/System.Text.Json/src/Resources/Strings.resx index ed8a9fd881cd..d2eff55202ba 100644 --- a/src/System.Text.Json/src/Resources/Strings.resx +++ b/src/System.Text.Json/src/Resources/Strings.resx @@ -204,9 +204,6 @@ The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing. - - The 'IBufferWriter' could not provide an output buffer that is large enough to continue writing. Need at least {0} bytes. - '{0}' is invalid after a value. Expected either ',', '}}', or ']'. @@ -312,4 +309,28 @@ The JSON value is not in a supported Guid format. + + '{0}' is an invalid start of a property name or value, after a comment. + + + The JSON array contains a trailing comma at the end which is not supported in this mode. Change the reader options. + + + The JSON object contains a trailing comma at the end which is not supported in this mode. Change the reader options. + + + Serializer options cannot be changed once serialization or deserialization has occurred. + + + Stream was not writable. + + + Cannot write a comment value which contains the end of comment delimiter. + + + The property '{0}.{1}' has the same name as a previous property based on naming or casing policies. + + + The property name for '{0}.{1}' cannot be null as a result of naming policies. + \ No newline at end of file diff --git a/src/System.Text.Json/src/System.Text.Json.csproj b/src/System.Text.Json/src/System.Text.Json.csproj index 0c8fe87203f2..c3c7e37c7f3a 100644 --- a/src/System.Text.Json/src/System.Text.Json.csproj +++ b/src/System.Text.Json/src/System.Text.Json.csproj @@ -9,6 +9,7 @@ $(DefineConstants);BUILDING_INBOX_LIBRARY + $(DefineConstants);USE_ABW_INTERNALLY @@ -41,12 +42,12 @@ - + @@ -64,14 +65,20 @@ + + + + + + @@ -86,9 +93,9 @@ - + @@ -101,7 +108,6 @@ - @@ -130,6 +136,12 @@ + + + + Common\System\Buffers\ArrayBufferWriter.cs + + diff --git a/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.MetadataDb.cs b/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.MetadataDb.cs index b87e81e933ae..3f608c473a2a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.MetadataDb.cs +++ b/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.MetadataDb.cs @@ -85,7 +85,20 @@ private struct MetadataDb : IDisposable private const int NumberOfRowsOffset = 8; internal int Length { get; private set; } - private byte[] _rentedBuffer; + private byte[] _data; +#if DEBUG + private bool _isLocked; +#endif + + internal MetadataDb(byte[] completeDb) + { + _data = completeDb; + Length = completeDb.Length; + +#if DEBUG + _isLocked = true; +#endif + } internal MetadataDb(int payloadLength) { @@ -107,22 +120,48 @@ internal MetadataDb(int payloadLength) initialSize = OneMegabyte; } - _rentedBuffer = ArrayPool.Shared.Rent(initialSize); + _data = ArrayPool.Shared.Rent(initialSize); Length = 0; +#if DEBUG + _isLocked = false; +#endif + } + + internal MetadataDb(MetadataDb source, bool useArrayPools) + { + Length = source.Length; + +#if DEBUG + _isLocked = !useArrayPools; +#endif + + if (useArrayPools) + { + _data = ArrayPool.Shared.Rent(Length); + source._data.AsSpan(0, Length).CopyTo(_data); + } + else + { + _data = source._data.AsSpan(0, Length).ToArray(); + } } public void Dispose() { - if (_rentedBuffer == null) + if (_data == null) { return; } +#if DEBUG + Debug.Assert(!_isLocked, "Dispose called on a locked database"); +#endif + // The data in this rented buffer only conveys the positions and // lengths of tokens in a document, but no content; so it does not // need to be cleared. - ArrayPool.Shared.Return(_rentedBuffer); - _rentedBuffer = null; + ArrayPool.Shared.Return(_data); + _data = null; Length = 0; } @@ -132,16 +171,16 @@ internal void TrimExcess() // amount of usage (particularly if Enlarge ever got called); and there's // the small copy-cost associated with trimming anyways. "Is half-empty" is // just a rough metric for "is trimming worth it?". - if (Length <= _rentedBuffer.Length / 2) + if (Length <= _data.Length / 2) { byte[] newRent = ArrayPool.Shared.Rent(Length); byte[] returnBuf = newRent; - if (newRent.Length < _rentedBuffer.Length) + if (newRent.Length < _data.Length) { - Buffer.BlockCopy(_rentedBuffer, 0, newRent, 0, Length); - returnBuf = _rentedBuffer; - _rentedBuffer = newRent; + Buffer.BlockCopy(_data, 0, newRent, 0, Length); + returnBuf = _data; + _data = newRent; } // The data in this rented buffer only conveys the positions and @@ -158,21 +197,25 @@ internal void Append(JsonTokenType tokenType, int startLocation, int length) (tokenType == JsonTokenType.StartArray || tokenType == JsonTokenType.StartObject) == (length == DbRow.UnknownSize)); - if (Length >= _rentedBuffer.Length - DbRow.Size) +#if DEBUG + Debug.Assert(!_isLocked, "Appending to a locked database"); +#endif + + if (Length >= _data.Length - DbRow.Size) { Enlarge(); } DbRow row = new DbRow(tokenType, startLocation, length); - MemoryMarshal.Write(_rentedBuffer.AsSpan(Length), ref row); + MemoryMarshal.Write(_data.AsSpan(Length), ref row); Length += DbRow.Size; } private void Enlarge() { - byte[] toReturn = _rentedBuffer; - _rentedBuffer = ArrayPool.Shared.Rent(toReturn.Length * 2); - Buffer.BlockCopy(toReturn, 0, _rentedBuffer, 0, toReturn.Length); + byte[] toReturn = _data; + _data = ArrayPool.Shared.Rent(toReturn.Length * 2); + Buffer.BlockCopy(toReturn, 0, _data, 0, toReturn.Length); // The data in this rented buffer only conveys the positions and // lengths of tokens in a document, but no content; so it does not @@ -192,7 +235,7 @@ internal void SetLength(int index, int length) { AssertValidIndex(index); Debug.Assert(length >= 0); - Span destination = _rentedBuffer.AsSpan(index + SizeOrLengthOffset); + Span destination = _data.AsSpan(index + SizeOrLengthOffset); MemoryMarshal.Write(destination, ref length); } @@ -201,7 +244,7 @@ internal void SetNumberOfRows(int index, int numberOfRows) AssertValidIndex(index); Debug.Assert(numberOfRows >= 1 && numberOfRows <= 0x0FFFFFFF); - Span dataPos = _rentedBuffer.AsSpan(index + NumberOfRowsOffset); + Span dataPos = _data.AsSpan(index + NumberOfRowsOffset); int current = MemoryMarshal.Read(dataPos); // Persist the most significant nybble @@ -214,7 +257,7 @@ internal void SetHasComplexChildren(int index) AssertValidIndex(index); // The HasComplexChildren bit is the most significant bit of "SizeOrLength" - Span dataPos = _rentedBuffer.AsSpan(index + SizeOrLengthOffset); + Span dataPos = _data.AsSpan(index + SizeOrLengthOffset); int current = MemoryMarshal.Read(dataPos); int value = current | unchecked((int)0x80000000); @@ -229,7 +272,7 @@ internal int FindIndexOfFirstUnsetSizeOrLength(JsonTokenType lookupType) private int FindOpenElement(JsonTokenType lookupType) { - Span data = _rentedBuffer.AsSpan(0, Length); + Span data = _data.AsSpan(0, Length); for (int i = Length - DbRow.Size; i >= 0; i -= DbRow.Size) { @@ -249,16 +292,72 @@ private int FindOpenElement(JsonTokenType lookupType) internal DbRow Get(int index) { AssertValidIndex(index); - return MemoryMarshal.Read(_rentedBuffer.AsSpan(index)); + return MemoryMarshal.Read(_data.AsSpan(index)); } internal JsonTokenType GetJsonTokenType(int index) { AssertValidIndex(index); - uint union = MemoryMarshal.Read(_rentedBuffer.AsSpan(index + NumberOfRowsOffset)); + uint union = MemoryMarshal.Read(_data.AsSpan(index + NumberOfRowsOffset)); return (JsonTokenType)(union >> 28); } + + internal MetadataDb CopySegment(int startIndex, int endIndex) + { + Debug.Assert( + endIndex > startIndex, + $"endIndex={endIndex} was at or before startIndex={startIndex}"); + + AssertValidIndex(startIndex); + Debug.Assert(endIndex <= Length); + + DbRow start = Get(startIndex); +#if DEBUG + DbRow end = Get(endIndex - DbRow.Size); + + if (start.TokenType == JsonTokenType.StartObject) + { + Debug.Assert( + end.TokenType == JsonTokenType.EndObject, + $"StartObject paired with {end.TokenType}"); + } + else if (start.TokenType == JsonTokenType.StartArray) + { + Debug.Assert( + end.TokenType == JsonTokenType.EndArray, + $"StartArray paired with {end.TokenType}"); + } + else + { + Debug.Assert( + startIndex + DbRow.Size == endIndex, + $"{start.TokenType} should have been one row"); + } +#endif + + int length = endIndex - startIndex; + + byte[] newDatabase = new byte[length]; + _data.AsSpan(startIndex, length).CopyTo(newDatabase); + + Span newDbInts = MemoryMarshal.Cast(newDatabase); + int locationOffset = newDbInts[0]; + + // Need to nudge one forward to account for the hidden quote on the string. + if (start.TokenType == JsonTokenType.String) + { + locationOffset--; + } + + for (int i = (length - DbRow.Size) / sizeof(int); i >= 0; i -= DbRow.Size / sizeof(int)) + { + Debug.Assert(newDbInts[i] >= locationOffset); + newDbInts[i] -= locationOffset; + } + + return new MetadataDb(newDatabase); + } } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs b/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs index fbfdc57798c9..65766b86531a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs +++ b/src/System.Text.Json/src/System/Text/Json/Document/JsonDocument.cs @@ -27,24 +27,38 @@ public sealed partial class JsonDocument : IDisposable private byte[] _extraRentedBytes; private (int, string) _lastIndexAndString = (-1, null); + /// + /// This is an implementation detail and MUST NOT be called by source-package consumers. + /// + internal bool IsDisposable { get; } + /// /// The representing the value of the document. /// public JsonElement RootElement => new JsonElement(this, 0); - private JsonDocument(ReadOnlyMemory utf8Json, MetadataDb parsedData, byte[] extraRentedBytes) + private JsonDocument( + ReadOnlyMemory utf8Json, + MetadataDb parsedData, + byte[] extraRentedBytes, + bool isDisposable = true) { Debug.Assert(!utf8Json.IsEmpty); _utf8Json = utf8Json; _parsedData = parsedData; _extraRentedBytes = extraRentedBytes; + + IsDisposable = isDisposable; + + // extraRentedBytes better be null if we're not disposable. + Debug.Assert(isDisposable || extraRentedBytes == null); } /// public void Dispose() { - if (_utf8Json.IsEmpty) + if (_utf8Json.IsEmpty || !IsDisposable) { return; } @@ -569,12 +583,27 @@ internal string GetPropertyRawValueAsString(int valueIndex) return JsonReaderHelper.TranscodeHelper(segment.Span); } + /// + /// This is an implementation detail and MUST NOT be called by source-package consumers. + /// + internal JsonElement CloneElement(int index) + { + int endIndex = GetEndIndex(index, true); + MetadataDb newDb = _parsedData.CopySegment(index, endIndex); + ReadOnlyMemory segmentCopy = GetRawValue(index, includeQuotes: true).ToArray(); + + JsonDocument newDocument = + new JsonDocument(segmentCopy, newDb, extraRentedBytes: null, isDisposable: false); + + return newDocument.RootElement; + } + /// /// This is an implementation detail and MUST NOT be called by source-package consumers. /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ReadOnlySpan propertyName) { CheckNotDisposed(); @@ -585,14 +614,14 @@ internal void WriteElementTo( { case JsonTokenType.StartObject: writer.WriteStartObject(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(propertyName, row, ref writer); + WriteString(propertyName, row, writer); return; case JsonTokenType.True: writer.WriteBoolean(propertyName, value: true); @@ -618,7 +647,7 @@ internal void WriteElementTo( /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ReadOnlySpan propertyName) { CheckNotDisposed(); @@ -629,14 +658,14 @@ internal void WriteElementTo( { case JsonTokenType.StartObject: writer.WriteStartObject(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(propertyName); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(propertyName, row, ref writer); + WriteString(propertyName, row, writer); return; case JsonTokenType.True: writer.WriteBoolean(propertyName, value: true); @@ -662,7 +691,7 @@ internal void WriteElementTo( /// internal void WriteElementTo( int index, - ref Utf8JsonWriter writer) + Utf8JsonWriter writer) { CheckNotDisposed(); @@ -672,14 +701,14 @@ internal void WriteElementTo( { case JsonTokenType.StartObject: writer.WriteStartObject(); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.StartArray: writer.WriteStartArray(); - WriteComplexElement(index, ref writer); + WriteComplexElement(index, writer); return; case JsonTokenType.String: - WriteString(row, ref writer); + WriteString(row, writer); return; case JsonTokenType.Number: writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span); @@ -698,7 +727,7 @@ internal void WriteElementTo( Debug.Fail($"Unexpected encounter with JsonTokenType {row.TokenType}"); } - private void WriteComplexElement(int index, ref Utf8JsonWriter writer) + private void WriteComplexElement(int index, Utf8JsonWriter writer) { int endIndex = GetEndIndex(index, true); @@ -710,7 +739,7 @@ private void WriteComplexElement(int index, ref Utf8JsonWriter writer) switch (row.TokenType) { case JsonTokenType.String: - WriteString(row, ref writer); + WriteString(row, writer); continue; case JsonTokenType.Number: writer.WriteNumberValue(_utf8Json.Slice(row.Location, row.SizeOrLength).Span); @@ -749,7 +778,7 @@ private void WriteComplexElement(int index, ref Utf8JsonWriter writer) switch (propertyValue.TokenType) { case JsonTokenType.String: - WriteString(propertyName, propertyValue, ref writer); + WriteString(propertyName, propertyValue, writer); continue; case JsonTokenType.Number: writer.WriteNumber( @@ -815,7 +844,7 @@ private static void ClearAndReturn(ArraySegment rented) } } - private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(ReadOnlySpan propertyName, in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; @@ -831,7 +860,7 @@ private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8 } } - private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(ReadOnlySpan propertyName, in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; @@ -848,7 +877,7 @@ private void WriteString(ReadOnlySpan propertyName, in DbRow row, ref Utf8 } } - private void WriteString(in DbRow row, ref Utf8JsonWriter writer) + private void WriteString(in DbRow row, Utf8JsonWriter writer) { ArraySegment rented = default; diff --git a/src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs b/src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs index 1c780a501668..1ff367ae3a45 100644 --- a/src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs +++ b/src/System.Text.Json/src/System/Text/Json/Document/JsonElement.cs @@ -338,9 +338,9 @@ public bool GetBoolean() /// /// This method does not create a string representation of values other than JSON strings. /// - /// The value of the element as a . + /// The value of the element as a . /// - /// This value's is not . + /// This value's is neither nor . /// /// /// The parent has been disposed. @@ -940,11 +940,11 @@ internal string GetPropertyRawText() /// /// The parent has been disposed. /// - public void WriteAsProperty(ReadOnlySpan propertyName, ref Utf8JsonWriter writer) + public void WriteAsProperty(ReadOnlySpan propertyName, Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer, propertyName); + _parent.WriteElementTo(_idx, writer, propertyName); } /// @@ -960,11 +960,11 @@ public void WriteAsProperty(ReadOnlySpan propertyName, ref Utf8JsonWriter /// /// The parent has been disposed. /// - public void WriteAsProperty(ReadOnlySpan utf8PropertyName, ref Utf8JsonWriter writer) + public void WriteAsProperty(ReadOnlySpan utf8PropertyName, Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer, utf8PropertyName); + _parent.WriteElementTo(_idx, writer, utf8PropertyName); } /// @@ -977,11 +977,11 @@ public void WriteAsProperty(ReadOnlySpan utf8PropertyName, ref Utf8JsonWri /// /// The parent has been disposed. /// - public void WriteAsValue(ref Utf8JsonWriter writer) + public void WriteAsValue(Utf8JsonWriter writer) { CheckValidInstance(); - _parent.WriteElementTo(_idx, ref writer); + _parent.WriteElementTo(_idx, writer); } /// @@ -1096,6 +1096,31 @@ public override string ToString() } } + /// + /// Get a JsonElement which can be safely stored beyond the lifetime of the + /// original . + /// + /// + /// A JsonElement which can be safely stored beyond the lifetime of the + /// original . + /// + /// + /// If this JsonElement is itself the output of a previous call to Clone, or + /// a value contained within another JsonElement which was the output of a previous + /// call to Clone, this method results in no additional memory allocation. + /// + public JsonElement Clone() + { + CheckValidInstance(); + + if (!_parent.IsDisposable) + { + return this; + } + + return _parent.CloneElement(_idx); + } + private void CheckValidInstance() { if (_parent == null) diff --git a/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs b/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs index e3cf75eaee3c..e5d1b836e938 100644 --- a/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs +++ b/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderOptions.cs @@ -15,7 +15,7 @@ public struct JsonReaderOptions /// /// Defines how the should handle comments when reading through the JSON. - /// By default the reader will throw a if it encounters a comment. + /// By default is thrown if a comment is encountered. /// public JsonCommentHandling CommentHandling { get; set; } @@ -34,5 +34,12 @@ public int MaxDepth _maxDepth = value; } } + + /// + /// Defines whether an extra comma at the end of a list of JSON values in an object or array + /// is allowed (and ignored) within the JSON payload being read. + /// By default, it's set to false, and is thrown if a trailing comma is encountered. + /// + public bool AllowTrailingCommas { get; set; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs b/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs index aee4920413c4..be9590588111 100644 --- a/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs +++ b/src/System.Text.Json/src/System/Text/Json/Reader/JsonReaderState.cs @@ -21,6 +21,7 @@ public struct JsonReaderState internal bool _isNotPrimitive; internal char _numberFormat; internal bool _stringHasEscaping; + internal bool _trailingCommaBeforeComment; internal JsonTokenType _tokenType; internal JsonTokenType _previousTokenType; internal JsonReaderOptions _readerOptions; @@ -64,6 +65,7 @@ public JsonReaderState(JsonReaderOptions options = default) _isNotPrimitive = default; _numberFormat = default; _stringHasEscaping = default; + _trailingCommaBeforeComment = default; _tokenType = default; _previousTokenType = default; _readerOptions = options; diff --git a/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs b/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs index 1997a9a006c0..70563bb817fe 100644 --- a/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs +++ b/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.MultiSegment.cs @@ -36,6 +36,7 @@ public Utf8JsonReader(in ReadOnlySequence jsonData, bool isFinalBlock, Jso _isNotPrimitive = state._isNotPrimitive; _numberFormat = state._numberFormat; _stringHasEscaping = state._stringHasEscaping; + _trailingCommaBeforeComment = state._trailingCommaBeforeComment; _tokenType = state._tokenType; _previousTokenType = state._previousTokenType; _readerOptions = state._readerOptions; @@ -373,6 +374,10 @@ private bool ConsumeValueMultiSegment(byte marker) { while (true) { + Debug.Assert((_trailingCommaBeforeComment && _readerOptions.CommentHandling == JsonCommentHandling.Allow) || !_trailingCommaBeforeComment); + Debug.Assert((_trailingCommaBeforeComment && marker != JsonConstants.Slash) || !_trailingCommaBeforeComment); + _trailingCommaBeforeComment = false; + if (marker == JsonConstants.Quote) { return ConsumeStringMultiSegment(); @@ -657,6 +662,8 @@ private bool ConsumeNumberMultiSegment() private bool ConsumePropertyNameMultiSegment() { + _trailingCommaBeforeComment = false; + if (!ConsumeStringMultiSegment()) { return false; @@ -1531,6 +1538,7 @@ private bool ConsumeNextTokenOrRollbackMultiSegment(byte marker) long prevLineNumber = _lineNumber; JsonTokenType prevTokenType = _tokenType; SequencePosition prevSequencePosition = _currentPosition; + bool prevTrailingCommaBeforeComment = _trailingCommaBeforeComment; ConsumeTokenResult result = ConsumeNextTokenMultiSegment(marker); if (result == ConsumeTokenResult.Success) { @@ -1544,6 +1552,7 @@ private bool ConsumeNextTokenOrRollbackMultiSegment(byte marker) _lineNumber = prevLineNumber; _totalConsumed = prevTotalConsumed; _currentPosition = prevSequencePosition; + _trailingCommaBeforeComment = prevTrailingCommaBeforeComment; } return false; } @@ -1619,6 +1628,7 @@ private ConsumeTokenResult ConsumeNextTokenMultiSegment(byte marker) if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash) { + _trailingCommaBeforeComment = true; return ConsumeCommentMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } @@ -1626,12 +1636,30 @@ private ConsumeTokenResult ConsumeNextTokenMultiSegment(byte marker) { if (first != JsonConstants.Quote) { + if (first == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + return ConsumeTokenResult.Success; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first); } return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } else { + if (first == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + return ConsumeTokenResult.Success; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } return ConsumeValueMultiSegment(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } } @@ -1652,6 +1680,9 @@ private ConsumeTokenResult ConsumeNextTokenMultiSegment(byte marker) private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment() { + Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Allow); + Debug.Assert(_tokenType == JsonTokenType.Comment); + if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType)) { _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray; @@ -1690,6 +1721,12 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment() if (first == JsonConstants.ListSeparator) { + // A comma without some JSON value preceding it is invalid + if (_previousTokenType <= JsonTokenType.StartObject || _previousTokenType == JsonTokenType.StartArray || _trailingCommaBeforeComment) + { + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueAfterComment, first); + } + _consumed++; _bytePositionInLine++; @@ -1726,10 +1763,33 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment() first = _buffer[_consumed]; } + if (first == JsonConstants.Slash) + { + _trailingCommaBeforeComment = true; + if (ConsumeCommentMultiSegment()) + { + goto Done; + } + else + { + goto RollBack; + } + } + if (_inObject) { if (first != JsonConstants.Quote) { + if (first == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first); } if (ConsumePropertyNameMultiSegment()) @@ -1743,6 +1803,16 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentTokenMultiSegment() } else { + if (first == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } + if (ConsumeValueMultiSegment(first)) { goto Done; @@ -2022,12 +2092,31 @@ private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkippedMultiS { if (marker != JsonConstants.Quote) { + if (marker == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker); } return ConsumePropertyNameMultiSegment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } else { + if (marker == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } return ConsumeValueMultiSegment(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } } diff --git a/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs b/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs index a7ea5263ea61..97bb44be3c4b 100644 --- a/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs +++ b/src/System.Text.Json/src/System/Text/Json/Reader/Utf8JsonReader.cs @@ -41,6 +41,7 @@ public ref partial struct Utf8JsonReader private bool _isLastSegment; internal bool _stringHasEscaping; private readonly bool _isMultiSegment; + private bool _trailingCommaBeforeComment; private SequencePosition _nextPosition; private SequencePosition _currentPosition; @@ -154,6 +155,7 @@ public SequencePosition Position _isNotPrimitive = _isNotPrimitive, _numberFormat = _numberFormat, _stringHasEscaping = _stringHasEscaping, + _trailingCommaBeforeComment = _trailingCommaBeforeComment, _tokenType = _tokenType, _previousTokenType = _previousTokenType, _readerOptions = _readerOptions, @@ -187,6 +189,7 @@ public Utf8JsonReader(ReadOnlySpan jsonData, bool isFinalBlock, JsonReader _isNotPrimitive = state._isNotPrimitive; _numberFormat = state._numberFormat; _stringHasEscaping = state._stringHasEscaping; + _trailingCommaBeforeComment = state._trailingCommaBeforeComment; _tokenType = state._tokenType; _previousTokenType = state._previousTokenType; _readerOptions = state._readerOptions; @@ -529,6 +532,15 @@ private void EndObject() if (!_inObject || _bitStack.CurrentDepth <= 0) ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBrace); + if (_trailingCommaBeforeComment) + { + if (!_readerOptions.AllowTrailingCommas) + { + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } + _trailingCommaBeforeComment = false; + } + _tokenType = JsonTokenType.EndObject; ValueSpan = _buffer.Slice(_consumed, 1); @@ -554,6 +566,15 @@ private void EndArray() if (_inObject || _bitStack.CurrentDepth <= 0) ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.MismatchedObjectArray, JsonConstants.CloseBracket); + if (_trailingCommaBeforeComment) + { + if (!_readerOptions.AllowTrailingCommas) + { + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } + _trailingCommaBeforeComment = false; + } + _tokenType = JsonTokenType.EndArray; ValueSpan = _buffer.Slice(_consumed, 1); @@ -798,6 +819,10 @@ private bool ConsumeValue(byte marker) { while (true) { + Debug.Assert((_trailingCommaBeforeComment && _readerOptions.CommentHandling == JsonCommentHandling.Allow) || !_trailingCommaBeforeComment); + Debug.Assert((_trailingCommaBeforeComment && marker != JsonConstants.Slash) || !_trailingCommaBeforeComment); + _trailingCommaBeforeComment = false; + if (marker == JsonConstants.Quote) { return ConsumeString(); @@ -978,6 +1003,8 @@ private bool ConsumeNumber() private bool ConsumePropertyName() { + _trailingCommaBeforeComment = false; + if (!ConsumeString()) { return false; @@ -1449,6 +1476,7 @@ private bool ConsumeNextTokenOrRollback(byte marker) long prevPosition = _bytePositionInLine; long prevLineNumber = _lineNumber; JsonTokenType prevTokenType = _tokenType; + bool prevTrailingCommaBeforeComment = _trailingCommaBeforeComment; ConsumeTokenResult result = ConsumeNextToken(marker); if (result == ConsumeTokenResult.Success) { @@ -1460,6 +1488,7 @@ private bool ConsumeNextTokenOrRollback(byte marker) _tokenType = prevTokenType; _bytePositionInLine = prevPosition; _lineNumber = prevLineNumber; + _trailingCommaBeforeComment = prevTrailingCommaBeforeComment; } return false; } @@ -1526,6 +1555,7 @@ private ConsumeTokenResult ConsumeNextToken(byte marker) if (_readerOptions.CommentHandling == JsonCommentHandling.Allow && first == JsonConstants.Slash) { + _trailingCommaBeforeComment = true; return ConsumeComment() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } @@ -1533,12 +1563,30 @@ private ConsumeTokenResult ConsumeNextToken(byte marker) { if (first != JsonConstants.Quote) { + if (first == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + return ConsumeTokenResult.Success; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first); } return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } else { + if (first == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + return ConsumeTokenResult.Success; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } return ConsumeValue(first) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } } @@ -1559,6 +1607,9 @@ private ConsumeTokenResult ConsumeNextToken(byte marker) private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken() { + Debug.Assert(_readerOptions.CommentHandling == JsonCommentHandling.Allow); + Debug.Assert(_tokenType == JsonTokenType.Comment); + if (JsonReaderHelper.IsTokenTypePrimitive(_previousTokenType)) { _tokenType = _inObject ? JsonTokenType.StartObject : JsonTokenType.StartArray; @@ -1597,6 +1648,12 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken() if (first == JsonConstants.ListSeparator) { + // A comma without some JSON value preceding it is invalid + if (_previousTokenType <= JsonTokenType.StartObject || _previousTokenType == JsonTokenType.StartArray || _trailingCommaBeforeComment) + { + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyOrValueAfterComment, first); + } + _consumed++; _bytePositionInLine++; @@ -1624,10 +1681,33 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken() first = _buffer[_consumed]; } + if (first == JsonConstants.Slash) + { + _trailingCommaBeforeComment = true; + if (ConsumeComment()) + { + goto Done; + } + else + { + goto RollBack; + } + } + if (_inObject) { if (first != JsonConstants.Quote) { + if (first == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, first); } if (ConsumePropertyName()) @@ -1641,6 +1721,16 @@ private ConsumeTokenResult ConsumeNextTokenFromLastNonCommentToken() } else { + if (first == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } + if (ConsumeValue(first)) { goto Done; @@ -1905,12 +1995,32 @@ private ConsumeTokenResult ConsumeNextTokenUntilAfterAllCommentsAreSkipped(byte { if (marker != JsonConstants.Quote) { + if (marker == JsonConstants.CloseBrace) + { + if (_readerOptions.AllowTrailingCommas) + { + EndObject(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd); + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.ExpectedStartOfPropertyNotFound, marker); } return ConsumePropertyName() ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } else { + if (marker == JsonConstants.CloseBracket) + { + if (_readerOptions.AllowTrailingCommas) + { + EndArray(); + goto Done; + } + ThrowHelper.ThrowJsonReaderException(ref this, ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd); + } + return ConsumeValue(marker) ? ConsumeTokenResult.Success : ConsumeTokenResult.NotEnoughDataRollBackState; } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs new file mode 100644 index 000000000000..afddd6510da1 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/DefaultEnumerableConverter.cs @@ -0,0 +1,90 @@ +using System.Collections; +using System.Collections.Generic; +using System.Text.Json.Serialization.Policies; + +namespace System.Text.Json.Serialization.Converters +{ + internal class JsonEnumerableT : ICollection, IEnumerable, IList, IReadOnlyCollection, IReadOnlyList + { + List _list; + + public JsonEnumerableT(IList sourceList) + { + // TODO: Change sourceList from IList to List so we can do a direct assignment here. + _list = new List(); + + foreach (object item in sourceList) + { + if (item is T itemT) + { + _list.Add(itemT); + } + } + } + + public T this[int index] { get => (T)_list[index]; set => _list[index] = value; } + + public int Count => _list.Count; + + public bool IsReadOnly => false; + + public void Add(T item) + { + _list.Add(item); + } + + public void Clear() + { + _list.Clear(); + } + + public bool Contains(T item) + { + return _list.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + _list.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return _list.GetEnumerator(); + } + + public int IndexOf(T item) + { + return _list.IndexOf(item); + } + + public void Insert(int index, T item) + { + _list.Insert(index, item); + } + + public bool Remove(T item) + { + return _list.Remove(item); + } + + public void RemoveAt(int index) + { + _list.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + internal sealed class DefaultEnumerableConverter : JsonEnumerableConverter + { + public override IEnumerable CreateFromList(Type elementType, IList sourceList) + { + Type t = typeof(JsonEnumerableT<>).MakeGenericType(elementType); + return (IEnumerable)Activator.CreateInstance(t, sourceList); + } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs index 918c1e4b3bd6..9526f67f1d6c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterBoolean.cs @@ -20,12 +20,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out bool return true; } - public override void Write(bool value, ref Utf8JsonWriter writer) + public override void Write(bool value, Utf8JsonWriter writer) { writer.WriteBooleanValue(value); } - public override void Write(Span escapedPropertyName, bool value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, bool value, Utf8JsonWriter writer) { writer.WriteBoolean(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs index ce2a95f758eb..174a87be114a 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterByte.cs @@ -22,12 +22,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out byte return true; } - public override void Write(byte value, ref Utf8JsonWriter writer) + public override void Write(byte value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, byte value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, byte value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs index 30aa4e7ba449..107274327381 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterChar.cs @@ -21,20 +21,20 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out char return true; } - public override void Write(char value, ref Utf8JsonWriter writer) + public override void Write(char value, Utf8JsonWriter writer) { #if BUILDING_INBOX_LIBRARY - Span temp = MemoryMarshal.CreateSpan(ref value, 1); + Span temp = MemoryMarshal.CreateSpan(ref value, 1); writer.WriteStringValue(temp); #else writer.WriteStringValue(value.ToString()); #endif } - public override void Write(Span escapedPropertyName, char value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, char value, Utf8JsonWriter writer) { #if BUILDING_INBOX_LIBRARY - Span temp = MemoryMarshal.CreateSpan(ref value, 1); + Span temp = MemoryMarshal.CreateSpan(ref value, 1); writer.WriteString(escapedPropertyName, temp); #else writer.WriteString(escapedPropertyName, value.ToString()); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs index 7b49f8ed0e2e..f15470ccb079 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTime.cs @@ -13,12 +13,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out Date return reader.TryGetDateTime(out value); } - public override void Write(DateTime value, ref Utf8JsonWriter writer) + public override void Write(DateTime value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, DateTime value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, DateTime value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs index a19b082146bd..0f9d6d3a0198 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDateTimeOffset.cs @@ -13,12 +13,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out Date return reader.TryGetDateTimeOffset(out value); } - public override void Write(DateTimeOffset value, ref Utf8JsonWriter writer) + public override void Write(DateTimeOffset value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, DateTimeOffset value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, DateTimeOffset value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs index b5dcb88dc61d..ed90587c06e9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDecimal.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out deci return reader.TryGetDecimal(out value); } - public override void Write(decimal value, ref Utf8JsonWriter writer) + public override void Write(decimal value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, decimal value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, decimal value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs index 91c78969a0f5..5d236966d135 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterDouble.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out doub return reader.TryGetDouble(out value); } - public override void Write(double value, ref Utf8JsonWriter writer) + public override void Write(double value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, double value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, double value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs index 10f08c1deef0..b99dcc905244 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterEnum.cs @@ -44,7 +44,7 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out TVal return true; } - public override void Write(TValue value, ref Utf8JsonWriter writer) + public override void Write(TValue value, Utf8JsonWriter writer) { if (TreatAsString) { @@ -64,7 +64,7 @@ public override void Write(TValue value, ref Utf8JsonWriter writer) } } - public override void Write(Span escapedPropertyName, TValue value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, TValue value, Utf8JsonWriter writer) { if (TreatAsString) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs index 0ddac2d8e4a2..16fe1d51340c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt16.cs @@ -22,12 +22,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out shor return true; } - public override void Write(short value, ref Utf8JsonWriter writer) + public override void Write(short value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, short value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, short value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs index 420d6b41cbfc..de2181824693 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt32.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out int return reader.TryGetInt32(out value); } - public override void Write(int value, ref Utf8JsonWriter writer) + public override void Write(int value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, int value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, int value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs index d5ca30693d39..b9d18dd7275e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterInt64.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out long return reader.TryGetInt64(out value); } - public override void Write(long value, ref Utf8JsonWriter writer) + public override void Write(long value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, long value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, long value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs index 4bbb0eacdd26..786a7408c3ba 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSByte.cs @@ -22,12 +22,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out sbyt return true; } - public override void Write(sbyte value, ref Utf8JsonWriter writer) + public override void Write(sbyte value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, sbyte value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, sbyte value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs index 48f75b73f7f1..32093e3bffa4 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterSingle.cs @@ -22,12 +22,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out floa return true; } - public override void Write(float value, ref Utf8JsonWriter writer) + public override void Write(float value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, float value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, float value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs index e89260254712..f5120cfe3bf8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs @@ -20,12 +20,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out stri return true; } - public override void Write(string value, ref Utf8JsonWriter writer) + public override void Write(string value, Utf8JsonWriter writer) { writer.WriteStringValue(value); } - public override void Write(Span escapedPropertyName, string value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, string value, Utf8JsonWriter writer) { writer.WriteString(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs index a16973125238..cb232e630a5f 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt16.cs @@ -22,12 +22,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out usho return true; } - public override void Write(ushort value, ref Utf8JsonWriter writer) + public override void Write(ushort value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, ushort value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, ushort value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs index 1337c56d273a..94e7dfccf4d6 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt32.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out uint return reader.TryGetUInt32(out value); } - public override void Write(uint value, ref Utf8JsonWriter writer) + public override void Write(uint value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, uint value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, uint value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs index 60ff3d1ab7bb..7a52f4b6252c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterUInt64.cs @@ -19,12 +19,12 @@ public override bool TryRead(Type valueType, ref Utf8JsonReader reader, out ulon return reader.TryGetUInt64(out value); } - public override void Write(ulong value, ref Utf8JsonWriter writer) + public override void Write(ulong value, Utf8JsonWriter writer) { writer.WriteNumberValue(value); } - public override void Write(Span escapedPropertyName, ulong value, ref Utf8JsonWriter writer) + public override void Write(Span escapedPropertyName, ulong value, Utf8JsonWriter writer) { writer.WriteNumber(escapedPropertyName, value); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs new file mode 100644 index 000000000000..8b4ca3d151ff --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonAttribute.cs @@ -0,0 +1,11 @@ +// 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. + +namespace System.Text.Json.Serialization +{ + /// + /// The base class of serialization attributes. + /// + public abstract class JsonAttribute : Attribute { } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamePolicy.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamePolicy.cs new file mode 100644 index 000000000000..c58fe987f4b8 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonCamelCaseNamePolicy.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + internal sealed class JsonCamelCaseNamePolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + if (string.IsNullOrEmpty(name) || !char.IsUpper(name[0])) + { + return name; + } + + char[] chars = name.ToCharArray(); + + for (int i = 0; i < chars.Length; i++) + { + if (i == 1 && !char.IsUpper(chars[i])) + { + break; + } + + bool hasNext = (i + 1 < chars.Length); + + // Stop when next char is already lowercase. + if (i > 0 && hasNext && !char.IsUpper(chars[i + 1])) + { + // If the next char is a space, lowercase current char before exiting. + if (chars[i + 1] == ' ') + { + chars[i] = char.ToLowerInvariant(chars[i]); + } + + break; + } + + chars[i] = char.ToLowerInvariant(chars[i]); + } + + return new string(chars); + } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs index 8b1af9306bdb..4f65a1d5a78d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.AddProperty.cs @@ -2,7 +2,6 @@ // 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.Buffers; using System.Diagnostics; using System.Reflection; @@ -10,46 +9,21 @@ namespace System.Text.Json.Serialization { internal partial class JsonClassInfo { - private void AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) + private JsonPropertyInfo AddProperty(Type propertyType, PropertyInfo propertyInfo, Type classType, JsonSerializerOptions options) { JsonPropertyInfo jsonInfo = CreateProperty(propertyType, propertyType, propertyInfo, classType, options); if (propertyInfo != null) { - string propertyName = propertyInfo.Name; - - // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder. - byte[] propertyNameBytes = Encoding.UTF8.GetBytes(propertyName); - jsonInfo._name = propertyNameBytes; - - // Cache the escaped name. - int valueIdx = JsonWriterHelper.NeedsEscaping(propertyNameBytes); - if (valueIdx == -1) - { - jsonInfo._escapedName = propertyNameBytes; - } - else - { - int length = JsonWriterHelper.GetMaxEscapedLength(propertyNameBytes.Length, valueIdx); - - byte[] tempArray = ArrayPool.Shared.Rent(length); - - JsonWriterHelper.EscapeString(propertyNameBytes, tempArray, valueIdx, out int written); - jsonInfo._escapedName = new byte[written]; - tempArray.CopyTo(jsonInfo._escapedName, 0); - - // We clear the array because it is "user data" (although a property name). - new Span(tempArray, 0, written).Clear(); - ArrayPool.Shared.Return(tempArray); - } - - _propertyRefs.Add(new PropertyRef(GetKey(propertyNameBytes), jsonInfo)); + _propertyRefs.Add(new PropertyRef(GetKey(jsonInfo.CompareName), jsonInfo)); } else { // A single property or an IEnumerable _propertyRefs.Add(new PropertyRef(0, jsonInfo)); } + + return jsonInfo; } internal JsonPropertyInfo CreateProperty(Type declaredPropertyType, Type runtimePropertyType, PropertyInfo propertyInfo, Type parentClassType, JsonSerializerOptions options) @@ -95,10 +69,7 @@ internal JsonPropertyInfo CreatePolymorphicProperty(JsonPropertyInfo property, T } JsonPropertyInfo runtimeProperty = CreateProperty(property.DeclaredPropertyType, runtimePropertyType, property?.PropertyInfo, Type, options); - - runtimeProperty._name = property._name; - runtimeProperty._escapedName = property._escapedName; - // Copy other settings here as they are added as features. + property.CopyRuntimeSettingsTo(runtimeProperty); return runtimeProperty; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs index 3ce5bbca4558..3baecf8052bb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonClassInfo.cs @@ -32,8 +32,11 @@ internal sealed partial class JsonClassInfo internal void UpdateSortedPropertyCache(ref ReadStackFrame frame) { + // Todo: on classes with many properties (about 50) we need to switch to a hashtable for performance. + // Todo: when using PropertyNameCaseInsensitive we also need to use the hashtable with case-insensitive + // comparison to handle Turkish etc. cultures properly. + // Set the sorted property cache. Overwrite any existing cache which can occur in multi-threaded cases. - // Todo: on classes with many properties (about 50) we need to switch to a hashtable. if (frame.PropertyRefCache != null) { List cache = frame.PropertyRefCache; @@ -83,13 +86,28 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options) // Ignore properties on enumerable. if (ClassType == ClassType.Object) { + var propertyNames = new HashSet(StringComparer.Ordinal); + foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { // For now we only support public getters\setters if (propertyInfo.GetMethod?.IsPublic == true || propertyInfo.SetMethod?.IsPublic == true) { - AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + JsonPropertyInfo jsonPropertyInfo = AddProperty(propertyInfo.PropertyType, propertyInfo, type, options); + + if (jsonPropertyInfo.NameAsString == null) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameNull(this, jsonPropertyInfo); + } + + // If the JsonPropertyNameAttribute or naming policy results in collisions, throw an exception. + if (!propertyNames.Add(jsonPropertyInfo.CompareNameAsString)) + { + ThrowHelper.ThrowInvalidOperationException_SerializerPropertyNameConflict(this, jsonPropertyInfo); + } + + jsonPropertyInfo.ClearUnusedValuesAfterAdd(); } } } @@ -114,8 +132,16 @@ internal JsonClassInfo(Type type, JsonSerializerOptions options) } } - internal JsonPropertyInfo GetProperty(ReadOnlySpan propertyName, ref ReadStackFrame frame) + internal JsonPropertyInfo GetProperty(JsonSerializerOptions options, ReadOnlySpan propertyName, ref ReadStackFrame frame) { + // If we should compare with case-insensitive, normalize to an uppercase format since that is what is cached on the propertyInfo. + if (options.PropertyNameCaseInsensitive) + { + string utf16PropertyName = Encoding.UTF8.GetString(propertyName.ToArray()); + string upper = utf16PropertyName.ToUpperInvariant(); + propertyName = Encoding.UTF8.GetBytes(upper); + } + ulong key = GetKey(propertyName); JsonPropertyInfo info = null; @@ -212,7 +238,7 @@ private static bool TryIsPropertyRefEqual(ref PropertyRef propertyRef, ReadOnlyS { if (propertyName.Length <= PropertyNameKeyLength || // We compare the whole name, although we could skip the first 6 bytes (but it's likely not any faster) - propertyName.SequenceEqual((ReadOnlySpan)propertyRef.Info._name)) + propertyName.SequenceEqual((ReadOnlySpan)propertyRef.Info.CompareName)) { info = propertyRef.Info; return true; @@ -238,8 +264,6 @@ private static bool IsPropertyRefEqual(ref PropertyRef propertyRef, PropertyRef private static ulong GetKey(ReadOnlySpan propertyName) { - Debug.Assert(propertyName.Length > 0); - ulong key; int length = propertyName.Length; @@ -264,18 +288,22 @@ private static ulong GetKey(ReadOnlySpan propertyName) key |= (ulong)propertyName[2] << 16; } } - else + else if (length == 1) { key = propertyName[0]; } + else + { + // An empty name is valid. + key = 0; + } // Embed the propertyName length in the last two bytes. key |= (ulong)propertyName.Length << 48; return key; - } - private static Type GetElementType(Type propertyType) + public static Type GetElementType(Type propertyType) { Type elementType = null; if (typeof(IEnumerable).IsAssignableFrom(propertyType)) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.cs new file mode 100644 index 000000000000..8644189a1313 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonIgnoreAttribute.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. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + /// + /// Prevents a property from being serialized or deserialized. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class JsonIgnoreAttribute : JsonAttribute + { + public JsonIgnoreAttribute() { } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs new file mode 100644 index 000000000000..ff4a2a360eb6 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonNamingPolicy.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + /// + /// Determines the naming policy used to convert a JSON name to another format, such as a camel-casing format. + /// + public abstract class JsonNamingPolicy + { + protected JsonNamingPolicy() { } + + /// + /// Returns the naming policy for camel-casing. + /// + public static JsonNamingPolicy CamelCase { get; } = new JsonCamelCaseNamePolicy(); + + /// + /// Converts the provided name. + /// + /// The name to convert. + /// The converted name. + public abstract string ConvertName(string name); + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs index 7201cba185d2..448f5eb220fb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfo.cs @@ -2,6 +2,9 @@ // 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.Buffers; +using System.Collections; +using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization.Converters; using System.Text.Json.Serialization.Policies; @@ -10,19 +13,33 @@ namespace System.Text.Json.Serialization { internal abstract class JsonPropertyInfo { - // For now, just a global converter. - private static JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultArrayConverter(); + // Cache the converters so they don't get created for every enumerable property. + private static readonly JsonEnumerableConverter s_jsonArrayConverter = new DefaultArrayConverter(); + private static readonly JsonEnumerableConverter s_jsonEnumerableConverter = new DefaultEnumerableConverter(); internal ClassType ClassType; - internal byte[] _name = default; - internal byte[] _escapedName = default; + // The name of the property with any casing policy or the name specified from JsonPropertyNameAttribute. + private byte[] _name { get; set; } + internal ReadOnlySpan Name => _name; + internal string NameAsString { get; private set; } + + // Used to support case-insensitive comparison + private byte[] _compareName { get; set; } + internal ReadOnlySpan CompareName => _compareName; + internal string CompareNameAsString { get; private set; } + + // The escaped name passed to the writer. + internal byte[] _escapedName { get; private set; } + internal ReadOnlySpan EscapedName => _escapedName; internal bool HasGetter { get; set; } internal bool HasSetter { get; set; } + internal bool ShouldSerialize { get; private set; } + internal bool ShouldDeserialize { get; private set; } + + internal bool IgnoreNullValues { get; private set; } - public ReadOnlySpan EscapedName => _escapedName; - public ReadOnlySpan Name => _name; // todo: to minimize hashtable lookups, cache JsonClassInfo: //public JsonClassInfo ClassInfo; @@ -45,6 +62,7 @@ internal JsonPropertyInfo( ClassType = JsonClassInfo.GetClassType(runtimePropertyType); if (elementType != null) { + Debug.Assert(ClassType == ClassType.Enumerable); ElementClassInfo = options.GetOrAddClass(elementType); } @@ -58,7 +76,7 @@ internal JsonPropertyInfo( internal bool IsNullableType { get; private set; } - public PropertyInfo PropertyInfo { get; private set; } + internal PropertyInfo PropertyInfo { get; private set; } internal Type ParentClassType { get; private set; } @@ -68,31 +86,177 @@ internal JsonPropertyInfo( internal virtual void GetPolicies(JsonSerializerOptions options) { - if (RuntimePropertyType.IsArray) + DetermineSerializationCapabilities(options); + DeterminePropertyName(options); + IgnoreNullValues = options.IgnoreNullValues; + } + + private void DeterminePropertyName(JsonSerializerOptions options) + { + if (PropertyInfo != null) { - EnumerableConverter = s_jsonEnumerableConverter; + JsonPropertyNameAttribute nameAttribute = GetAttribute(); + if (nameAttribute != null) + { + NameAsString = nameAttribute.Name; + + // This is detected and thrown by caller. + if (NameAsString == null) + { + return; + } + } + else if (options.PropertyNamingPolicy != null) + { + NameAsString = options.PropertyNamingPolicy.ConvertName(PropertyInfo.Name); + + // This is detected and thrown by caller. + if (NameAsString == null) + { + return; + } + } + else + { + NameAsString = PropertyInfo.Name; + } + + // At this point propertyName is valid UTF16, so just call the simple UTF16->UTF8 encoder. + _name = Encoding.UTF8.GetBytes(NameAsString); + + // Set the compare name. + if (options.PropertyNameCaseInsensitive) + { + CompareNameAsString = NameAsString.ToUpperInvariant(); + _compareName = Encoding.UTF8.GetBytes(CompareNameAsString); + } + else + { + CompareNameAsString = NameAsString; + _compareName = _name; + } + + // Cache the escaped name. + int valueIdx = JsonWriterHelper.NeedsEscaping(_name); + if (valueIdx == -1) + { + _escapedName = _name; + } + else + { + int length = JsonWriterHelper.GetMaxEscapedLength(_name.Length, valueIdx); + + byte[] tempArray = ArrayPool.Shared.Rent(length); + + JsonWriterHelper.EscapeString(_name, tempArray, valueIdx, out int written); + _escapedName = new byte[written]; + tempArray.CopyTo(_escapedName, 0); + + // We clear the array because it is "user data" (although a property name). + new Span(tempArray, 0, written).Clear(); + ArrayPool.Shared.Return(tempArray); + } } } - internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options); + private void DetermineSerializationCapabilities(JsonSerializerOptions options) + { + bool hasIgnoreAttribute = (GetAttribute() != null); + + if (hasIgnoreAttribute) + { + // We don't serialize or deserialize. + return; + } - internal bool IgnoreNullPropertyValueOnRead(JsonSerializerOptions options) + if (ClassType != ClassType.Enumerable) + { + // We serialize if there is a getter + no [Ignore] attribute + not ignoring readonly properties. + ShouldSerialize = HasGetter && (HasSetter || !options.IgnoreReadOnlyProperties); + + // We deserialize if there is a setter + no [Ignore] attribute. + ShouldDeserialize = HasSetter; + } + else + { + if (HasGetter) + { + if (HasSetter) + { + ShouldDeserialize = true; + } + else if (RuntimePropertyType.IsAssignableFrom(typeof(IList))) + { + ShouldDeserialize = true; + } + //else + //{ + // // todo: future feature that allows non-List types (e.g. from System.Collections.Immutable) to have converters. + //} + } + //else if (HasSetter) + //{ + // // todo: Special case where there is no getter but a setter (and an EnumerableConverter) + //} + + if (ShouldDeserialize) + { + ShouldSerialize = HasGetter; + + if (RuntimePropertyType.IsArray) + { + EnumerableConverter = s_jsonArrayConverter; + } + else if (typeof(IEnumerable).IsAssignableFrom(RuntimePropertyType)) + { + Type elementType = JsonClassInfo.GetElementType(RuntimePropertyType); + + // If the property type only has interface(s) exposed by JsonEnumerableT then use JsonEnumerableT as the converter. + if (RuntimePropertyType.IsAssignableFrom(typeof(JsonEnumerableT<>).MakeGenericType(elementType))) + { + EnumerableConverter = s_jsonEnumerableConverter; + } + } + } + else + { + ShouldSerialize = HasGetter && !options.IgnoreReadOnlyProperties; + } + } + } + + // After the property is added, clear any state not used later. + internal void ClearUnusedValuesAfterAdd() { - return options.IgnoreNullPropertyValueOnRead; + NameAsString = null; + CompareNameAsString = null; } - internal bool IgnoreNullPropertyValueOnWrite(JsonSerializerOptions options) + // Copy any settings defined at run-time to the new property. + internal void CopyRuntimeSettingsTo(JsonPropertyInfo other) { - return options.IgnoreNullPropertyValueOnWrite; + other._name = _name; + other._compareName = _compareName; + other._escapedName = _escapedName; } - internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); + internal abstract object GetValueAsObject(object obj, JsonSerializerOptions options); + + internal TAttribute GetAttribute() where TAttribute : Attribute + { + return (TAttribute)PropertyInfo?.GetCustomAttribute(typeof(TAttribute), inherit: false); + } + internal abstract void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state); + + internal abstract IList CreateConverterList(); + + internal abstract void Read(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader); internal abstract void SetValueAsObject(object obj, object value, JsonSerializerOptions options); - internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer); + internal abstract void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); - internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer); + internal abstract void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs index a90b880a98d7..b7365a8f7aea 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoCommon.cs @@ -2,6 +2,8 @@ // 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.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Text.Json.Serialization.Converters; @@ -78,10 +80,15 @@ internal override void SetValueAsObject(object obj, object value, JsonSerializer Debug.Assert(Set != null); TDeclaredProperty typedValue = (TDeclaredProperty)value; - if (typedValue != null || !IgnoreNullPropertyValueOnWrite(options)) + if (typedValue != null || !IgnoreNullValues) { Set((TClass)obj, (TDeclaredProperty)value); } } + + internal override IList CreateConverterList() + { + return new List(); + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs index 8a17d0056f15..ce4a94fcda26 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNotNullable.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -36,7 +37,7 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader); } - else if (HasSetter) + else if (ShouldDeserialize) { if (ValueConverter != null) { @@ -48,10 +49,10 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio } else { - if (value != null || !IgnoreNullPropertyValueOnRead(options)) - { - Set((TClass)state.Current.ReturnValue, value); - } + // Null values were already handled. + Debug.Assert(value != null); + + Set((TClass)state.Current.ReturnValue, value); } return; @@ -62,30 +63,34 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio } } + // If this method is changed, also change JsonPropertyInfoNullable.ReadEnumerable and JsonSerializer.ApplyObjectToEnumerable internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (ValueConverter != null) + if (ValueConverter == null || !ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value)) { - if (ValueConverter.TryRead(RuntimePropertyType, ref reader, out TRuntimeProperty value)) - { - ReadStackFrame.SetReturnValue(value, options, ref state.Current); - return; - } + ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + return; } - ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + JsonSerializer.ApplyValueToEnumerable(ref value, options, ref state.Current); + } + + internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) + { + Debug.Assert(state.Current.JsonPropertyInfo != null); + state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options); } // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call. - internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (current.Enumerator != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); - propertyInfo.WriteEnumerable(options, ref current, ref writer); + propertyInfo.WriteEnumerable(options, ref current, writer); } - else if (HasGetter) + else if (ShouldSerialize) { TRuntimeProperty value; if (_isPropertyPolicy) @@ -103,7 +108,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame { writer.WriteNullValue(); } - else if (!IgnoreNullPropertyValueOnWrite(options)) + else if (!IgnoreNullValues) { writer.WriteNull(_escapedName); } @@ -112,29 +117,40 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame { if (_escapedName != null) { - ValueConverter.Write(_escapedName, value, ref writer); + ValueConverter.Write(_escapedName, value, writer); } else { - ValueConverter.Write(value, ref writer); + ValueConverter.Write(value, writer); } } } } - internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) { Debug.Assert(current.Enumerator != null); - TRuntimeProperty value = (TRuntimeProperty)current.Enumerator.Current; + + TRuntimeProperty value; + if (current.Enumerator is IEnumerator enumerator) + { + // Avoid boxing for strongly-typed enumerators such as returned from IList. + value = enumerator.Current; + } + else + { + value = (TRuntimeProperty)current.Enumerator.Current; + } + if (value == null) { writer.WriteNullValue(); } else { - ValueConverter.Write(value, ref writer); + ValueConverter.Write(value, writer); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs index 06c6473b4859..44cc4365b673 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyInfoNullable.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using System.Diagnostics; using System.Reflection; @@ -10,7 +11,7 @@ namespace System.Text.Json.Serialization /// /// Represents a strongly-typed property that is a . /// - internal sealed class JsonPropertyInfoNullable + internal sealed class JsonPropertyInfoNullable : JsonPropertyInfoCommon where TProperty : struct { @@ -36,7 +37,7 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); propertyInfo.ReadEnumerable(tokenType, options, ref state, ref reader); } - else if (HasSetter) + else if (ShouldDeserialize) { if (ValueConverter != null) { @@ -61,28 +62,33 @@ internal override void Read(JsonTokenType tokenType, JsonSerializerOptions optio internal override void ReadEnumerable(JsonTokenType tokenType, JsonSerializerOptions options, ref ReadStack state, ref Utf8JsonReader reader) { - if (ValueConverter != null) + if (ValueConverter == null || !ValueConverter.TryRead(typeof(TProperty), ref reader, out TProperty value)) { - if (ValueConverter.TryRead(s_underlyingType, ref reader, out TProperty value)) - { - ReadStackFrame.SetReturnValue(value, options, ref state.Current); - return; - } + ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + return; } - ThrowHelper.ThrowJsonReaderException_DeserializeUnableToConvertValue(RuntimePropertyType, reader, state); + // Converting to TProperty? here lets us share a common ApplyValue() with ApplyNullValue(). + TProperty? nullableValue = new TProperty?(value); + JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state.Current); + } + + internal override void ApplyNullValue(JsonSerializerOptions options, ref ReadStack state) + { + TProperty? nullableValue = null; + JsonSerializer.ApplyValueToEnumerable(ref nullableValue, options, ref state.Current); } // todo: have the caller check if current.Enumerator != null and call WriteEnumerable of the underlying property directly to avoid an extra virtual call. - internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void Write(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (current.Enumerator != null) { // Forward the setter to the value-based JsonPropertyInfo. JsonPropertyInfo propertyInfo = ElementClassInfo.GetPolicyProperty(); - propertyInfo.WriteEnumerable(options, ref current, ref writer); + propertyInfo.WriteEnumerable(options, ref current, writer); } - else if (HasGetter) + else if (ShouldSerialize) { TProperty? value; if (_isPropertyPolicy) @@ -100,7 +106,7 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame { writer.WriteNullValue(); } - else if (!IgnoreNullPropertyValueOnWrite(options)) + else if (!IgnoreNullValues) { writer.WriteNull(_escapedName); } @@ -109,29 +115,40 @@ internal override void Write(JsonSerializerOptions options, ref WriteStackFrame { if (_escapedName != null) { - ValueConverter.Write(_escapedName, value.GetValueOrDefault(), ref writer); + ValueConverter.Write(_escapedName, value.GetValueOrDefault(), writer); } else { - ValueConverter.Write(value.GetValueOrDefault(), ref writer); + ValueConverter.Write(value.GetValueOrDefault(), writer); } } } } - internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, ref Utf8JsonWriter writer) + internal override void WriteEnumerable(JsonSerializerOptions options, ref WriteStackFrame current, Utf8JsonWriter writer) { if (ValueConverter != null) { Debug.Assert(current.Enumerator != null); - TProperty? value = (TProperty?)current.Enumerator.Current; + + TProperty? value; + if (current.Enumerator is IEnumerator enumerator) + { + // Avoid boxing for strongly-typed enumerators such as returned from IList. + value = enumerator.Current; + } + else + { + value = (TProperty?)current.Enumerator.Current; + } + if (value == null) { writer.WriteNullValue(); } else { - ValueConverter.Write(value.GetValueOrDefault(), ref writer); + ValueConverter.Write(value.GetValueOrDefault(), writer); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyNameAttribute.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyNameAttribute.cs new file mode 100644 index 000000000000..e3a5ecb16b07 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonPropertyNameAttribute.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + /// + /// Specifies the property name that is present in the JSON when serializing and deserializing. + /// This overrides any naming policy specified by . + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] + public sealed class JsonPropertyNameAttribute : JsonAttribute + { + /// + /// Initializes a new instance of with the specified property name. + /// + /// The name of the property. + public JsonPropertyNameAttribute(string propertyName) + { + Name = propertyName; + } + + /// + /// The name of the property. + /// + public string Name { get; set; } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs index 5035e4efd931..ebd9fef785fd 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleArray.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Text.Json.Serialization.Policies; @@ -15,7 +16,10 @@ private static void HandleStartArray( ref Utf8JsonReader reader, ref ReadStack state) { - if (state.Current.Skip()) + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + + bool skip = jsonPropertyInfo != null && !jsonPropertyInfo.ShouldDeserialize; + if (skip || state.Current.Skip()) { // The array is not being applied to the object. state.Push(); @@ -23,7 +27,6 @@ private static void HandleStartArray( return; } - JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; if (jsonPropertyInfo == null || state.Current.JsonClassInfo.ClassType == ClassType.Unknown) { jsonPropertyInfo = state.Current.JsonClassInfo.CreatePolymorphicProperty(jsonPropertyInfo, typeof(object), options); @@ -53,8 +56,10 @@ private static void HandleStartArray( state.Current.EnumerableCreated = true; } + jsonPropertyInfo = state.Current.JsonPropertyInfo; + // If current property is already set (from a constructor, for example) leave as-is - if (state.Current.JsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null) + if (jsonPropertyInfo.GetValueAsObject(state.Current.ReturnValue, options) == null) { // Create the enumerable. object value = ReadStackFrame.CreateEnumerableValue(ref reader, ref state, options); @@ -133,7 +138,7 @@ private static bool HandleEndArray( // else there must be an outer object, so we'll return false here. } - ReadStackFrame.SetReturnValue(value, options, ref state.Current, setPropertyDirectly: setPropertyDirectly); + ApplyObjectToEnumerable(value, options, ref state.Current, setPropertyDirectly: setPropertyDirectly); if (!valueReturning) { @@ -142,5 +147,73 @@ private static bool HandleEndArray( return false; } + + // If this method is changed, also change ApplyValueToEnumerable. + internal static void ApplyObjectToEnumerable(object value, JsonSerializerOptions options, ref ReadStackFrame frame, bool setPropertyDirectly = false) + { + if (frame.IsEnumerable()) + { + if (frame.TempEnumerableValues != null) + { + frame.TempEnumerableValues.Add(value); + } + else + { + ((IList)frame.ReturnValue).Add(value); + } + } + else if (!setPropertyDirectly && frame.IsPropertyEnumerable()) + { + Debug.Assert(frame.JsonPropertyInfo != null); + Debug.Assert(frame.ReturnValue != null); + if (frame.TempEnumerableValues != null) + { + frame.TempEnumerableValues.Add(value); + } + else + { + ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); + } + } + else + { + Debug.Assert(frame.JsonPropertyInfo != null); + frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); + } + } + + // If this method is changed, also change ApplyObjectToEnumerable. + internal static void ApplyValueToEnumerable(ref TProperty value, JsonSerializerOptions options, ref ReadStackFrame frame) + { + if (frame.IsEnumerable()) + { + if (frame.TempEnumerableValues != null) + { + ((IList)frame.TempEnumerableValues).Add(value); + } + else + { + ((IList)frame.ReturnValue).Add(value); + } + } + else if (frame.IsPropertyEnumerable()) + { + Debug.Assert(frame.JsonPropertyInfo != null); + Debug.Assert(frame.ReturnValue != null); + if (frame.TempEnumerableValues != null) + { + ((IList)frame.TempEnumerableValues).Add(value); + } + else + { + ((IList)frame.JsonPropertyInfo.GetValueAsObject(frame.ReturnValue, options)).Add(value); + } + } + else + { + Debug.Assert(frame.JsonPropertyInfo != null); + frame.JsonPropertyInfo.SetValueAsObject(frame.ReturnValue, value, options); + } + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs index 16c07e52edc4..6f1d136fb1a5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs @@ -2,6 +2,7 @@ // 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.Collections; using System.Diagnostics; namespace System.Text.Json.Serialization @@ -29,9 +30,15 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J ThrowHelper.ThrowJsonReaderException_DeserializeCannotBeNull(reader, state); } - if (state.Current.IsEnumerable() || state.Current.IsPropertyEnumerable()) + if (state.Current.IsEnumerable()) { - ReadStackFrame.SetReturnValue(null, options, ref state.Current); + ApplyObjectToEnumerable(null, options, ref state.Current); + return false; + } + + if (state.Current.IsPropertyEnumerable()) + { + state.Current.JsonPropertyInfo.ApplyNullValue(options, ref state); return false; } @@ -41,7 +48,7 @@ private static bool HandleNull(ref Utf8JsonReader reader, ref ReadStack state, J return true; } - if (!propertyInfo.IgnoreNullPropertyValueOnRead(options)) + if (!propertyInfo.IgnoreNullValues) { state.Current.JsonPropertyInfo.SetValueAsObject(state.Current.ReturnValue, null, options); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs index afaf9c4a4b57..c5dd88dd2b89 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleObject.cs @@ -2,6 +2,8 @@ // 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.Collections; + namespace System.Text.Json.Serialization { public static partial class JsonSerializer @@ -55,7 +57,7 @@ private static bool HandleEndObject(JsonSerializerOptions options, ref ReadStack } state.Pop(); - ReadStackFrame.SetReturnValue(value, options, ref state.Current); + ApplyObjectToEnumerable(value, options, ref state.Current); return false; } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs new file mode 100644 index 000000000000..767d58e96856 --- /dev/null +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Helpers.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Text.Json.Serialization +{ + public static partial class JsonSerializer + { + private static object ReadCore( + Type returnType, + JsonSerializerOptions options, + ref Utf8JsonReader reader) + { + if (options == null) + { + options = JsonSerializerOptions.s_defaultOptions; + } + + ReadStack state = default; + state.Current.Initialize(returnType, options); + + ReadCore(options, ref reader, ref state); + + return state.Current.ReturnValue; + } + } +} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs index c77c1cb5c765..d39c8b4d7e18 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Span.cs @@ -49,9 +49,11 @@ public static object Parse(ReadOnlySpan utf8Json, Type returnType, JsonSer private static object ParseCore(ReadOnlySpan utf8Json, Type returnType, JsonSerializerOptions options) { if (options == null) - options = s_defaultSettings; + { + options = JsonSerializerOptions.s_defaultOptions; + } - var readerState = new JsonReaderState(options: options.ReaderOptions); + var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(utf8Json, isFinalBlock: true, readerState); object result = ReadCore(returnType, options, ref reader); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs index d44faacae4ea..b067059f79cb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.Stream.cs @@ -78,12 +78,15 @@ private static async ValueTask ReadAsync( JsonSerializerOptions options = null, CancellationToken cancellationToken = default) { - options ??= s_defaultSettings; + if (options == null) + { + options = JsonSerializerOptions.s_defaultOptions; + } ReadStack state = default; state.Current.Initialize(returnType, options); - var readerState = new JsonReaderState(options.ReaderOptions); + var readerState = new JsonReaderState(options.GetReaderOptions()); // todo: switch to ArrayBuffer implementation to handle and simplify the allocs? byte[] buffer = ArrayPool.Shared.Rent(options.DefaultBufferSize); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs index 095a3945037b..1e58137bd4d8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.String.cs @@ -63,11 +63,13 @@ public static object Parse(string json, Type returnType, JsonSerializerOptions o private static object ParseCore(string json, Type returnType, JsonSerializerOptions options = null) { if (options == null) - options = s_defaultSettings; + { + options = JsonSerializerOptions.s_defaultOptions; + } // todo: use an array pool here for smaller requests to avoid the alloc? byte[] jsonBytes = JsonReaderHelper.s_utf8Encoding.GetBytes(json); - var readerState = new JsonReaderState(options: options.ReaderOptions); + var readerState = new JsonReaderState(options.GetReaderOptions()); var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, readerState); object result = ReadCore(returnType, options, ref reader); diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs index 295861e5ce70..e31204546eca 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.cs @@ -14,23 +14,6 @@ namespace System.Text.Json.Serialization public static partial class JsonSerializer { internal static readonly JsonPropertyInfo s_missingProperty = new JsonPropertyInfoNotNullable(); - private static readonly JsonSerializerOptions s_defaultSettings = new JsonSerializerOptions(); - - private static object ReadCore( - Type returnType, - JsonSerializerOptions options, - ref Utf8JsonReader reader) - { - if (options == null) - options = s_defaultSettings; - - ReadStack state = default; - state.Current.Initialize(returnType, options); - - ReadCore(options, ref reader, ref state); - - return state.Current.ReturnValue; - } // todo: for readability, refactor this method to split by ClassType(Enumerable, Object, or Value) like Write() private static void ReadCore( @@ -59,7 +42,7 @@ private static void ReadCore( Debug.Assert(state.Current.JsonClassInfo != default); ReadOnlySpan propertyName = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(propertyName, ref state.Current); + state.Current.JsonPropertyInfo = state.Current.JsonClassInfo.GetProperty(options, propertyName, ref state.Current); if (state.Current.JsonPropertyInfo == null) { state.Current.JsonPropertyInfo = s_missingProperty; diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs index ef65d72a882f..da494eff2528 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleEnumerable.cs @@ -9,44 +9,45 @@ namespace System.Text.Json.Serialization { public static partial class JsonSerializer { - private static bool WriteEnumerable( - JsonSerializerOptions options, - ref Utf8JsonWriter writer, - ref WriteStack state) - { - return HandleEnumerable(state.Current.JsonClassInfo.ElementClassInfo, options, ref writer, ref state); - } - private static bool HandleEnumerable( JsonClassInfo elementClassInfo, JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { Debug.Assert(state.Current.JsonPropertyInfo.ClassType == ClassType.Enumerable); JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + if (!jsonPropertyInfo.ShouldSerialize) + { + // Ignore writing this property. + return true; + } if (state.Current.Enumerator == null) { - if (jsonPropertyInfo._name == null) - { - writer.WriteStartArray(); - } - else + IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + + if (enumerable == null) { - writer.WriteStartArray(jsonPropertyInfo._name); + // Write a null object or enumerable. + writer.WriteNull(jsonPropertyInfo.Name); + return true; } - IEnumerable enumerable = (IEnumerable)jsonPropertyInfo.GetValueAsObject(state.Current.CurrentValue, options); + state.Current.Enumerator = enumerable.GetEnumerator(); - if (enumerable != null) + if (jsonPropertyInfo.Name == null) + { + writer.WriteStartArray(); + } + else { - state.Current.Enumerator = enumerable.GetEnumerator(); + writer.WriteStartArray(jsonPropertyInfo.Name); } } - if (state.Current.Enumerator != null && state.Current.Enumerator.MoveNext()) + if (state.Current.Enumerator.MoveNext()) { // Check for polymorphism. if (elementClassInfo.ClassType == ClassType.Unknown) @@ -57,7 +58,7 @@ private static bool HandleEnumerable( if (elementClassInfo.ClassType == ClassType.Value) { - elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, ref writer); + elementClassInfo.GetPolicyProperty().WriteEnumerable(options, ref state.Current, writer); } else if (state.Current.Enumerator.Current == null) { diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs index beec923af9e1..17c5c1c21dc8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleObject.cs @@ -10,7 +10,7 @@ public static partial class JsonSerializer { private static bool WriteObject( JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { JsonClassInfo classInfo = state.Current.JsonClassInfo; @@ -32,7 +32,7 @@ private static bool WriteObject( // Determine if we are done enumerating properties. if (state.Current.PropertyIndex != classInfo.PropertyCount) { - HandleObject(options, ref writer, ref state); + HandleObject(options, writer, ref state); return false; } @@ -52,7 +52,7 @@ private static bool WriteObject( private static bool HandleObject( JsonSerializerOptions options, - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, ref WriteStack state) { Debug.Assert( @@ -76,7 +76,7 @@ private static bool HandleObject( if (jsonPropertyInfo.ClassType == ClassType.Value) { - jsonPropertyInfo.Write(options, ref state.Current, ref writer); + jsonPropertyInfo.Write(options, ref state.Current, writer); state.Current.NextProperty(); return true; } @@ -84,7 +84,7 @@ private static bool HandleObject( // A property that returns an enumerator keeps the same stack frame. if (jsonPropertyInfo.ClassType == ClassType.Enumerable) { - bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, ref writer, ref state); + bool endOfEnumerable = HandleEnumerable(jsonPropertyInfo.ElementClassInfo, options, writer, ref state); if (endOfEnumerable) { state.Current.NextProperty(); @@ -114,7 +114,7 @@ private static bool HandleObject( } else { - if (!jsonPropertyInfo.IgnoreNullPropertyValueOnWrite(options)) + if (!jsonPropertyInfo.IgnoreNullValues) { writer.WriteNull(jsonPropertyInfo._escapedName); } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs deleted file mode 100644 index 5024f2682324..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.HandleValue.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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.Diagnostics; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Serialization -{ - public static partial class JsonSerializer - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool WriteValue( - JsonSerializerOptions options, - ref Utf8JsonWriter writer, - ref WriteStackFrame current) - { - Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); - - current.JsonPropertyInfo.Write(options, ref current, ref writer); - return true; - } - } -} diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs index d0c9b74b907b..ab80dd5b2449 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Helpers.cs @@ -2,7 +2,6 @@ // 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.Buffers; using System.Diagnostics; namespace System.Text.Json.Serialization @@ -55,23 +54,16 @@ private static void VerifyValueAndType(object value, Type type) } } - private static void WriteNull( - ref JsonWriterState writerState, - IBufferWriter bufferWriter) - { - Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState); - writer.WriteNullValue(); - writer.Flush(true); - } - private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOptions options) { if (options == null) - options = s_defaultSettings; + { + options = JsonSerializerOptions.s_defaultOptions; + } byte[] result; - using (var output = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var output = new PooledBufferWriter(options.DefaultBufferSize)) { WriteCore(output, value, type, options); result = output.WrittenMemory.ToArray(); @@ -83,11 +75,13 @@ private static byte[] WriteCoreBytes(object value, Type type, JsonSerializerOpti private static string WriteCoreString(object value, Type type, JsonSerializerOptions options) { if (options == null) - options = s_defaultSettings; + { + options = JsonSerializerOptions.s_defaultOptions; + } string result; - using (var output = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var output = new PooledBufferWriter(options.DefaultBufferSize)) { WriteCore(output, value, type, options); result = JsonReaderHelper.TranscodeHelper(output.WrittenMemory.Span); @@ -96,12 +90,11 @@ private static string WriteCoreString(object value, Type type, JsonSerializerOpt return result; } - private static void WriteCore(ArrayBufferWriter output, object value, Type type, JsonSerializerOptions options) + private static void WriteCore(PooledBufferWriter output, object value, Type type, JsonSerializerOptions options) { Debug.Assert(type != null || value == null); - var writerState = new JsonWriterState(options.WriterOptions); - var writer = new Utf8JsonWriter(output, writerState); + var writer = new Utf8JsonWriter(output, options.GetWriterOptions()); if (value == null) { @@ -119,10 +112,10 @@ private static void WriteCore(ArrayBufferWriter output, object value, Type state.Current.Initialize(type, options); state.Current.CurrentValue = value; - Write(ref writer, -1, options, ref state); + Write(writer, -1, options, ref state); } - writer.Flush(isFinalBlock: true); + writer.Flush(); } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs index 2990306fc1ce..6a53e3aa8ed8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.Stream.cs @@ -46,15 +46,20 @@ public static Task WriteAsync(object value, Type type, Stream utf8Json, JsonSeri private static async Task WriteAsyncCore(object value, Type type, Stream utf8Json, JsonSerializerOptions options, CancellationToken cancellationToken) { if (options == null) - options = s_defaultSettings; + { + options = JsonSerializerOptions.s_defaultOptions; + } - var writerState = new JsonWriterState(options.WriterOptions); + JsonWriterOptions writerOptions = options.GetWriterOptions(); - using (var bufferWriter = new ArrayBufferWriter(options.DefaultBufferSize)) + using (var bufferWriter = new PooledBufferWriter(options.DefaultBufferSize)) + using (var writer = new Utf8JsonWriter(bufferWriter, writerOptions)) { if (value == null) { - WriteNull(ref writerState, bufferWriter); + writer.WriteNullValue(); + writer.Flush(); + #if BUILDING_INBOX_LIBRARY await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); #else @@ -80,7 +85,9 @@ private static async Task WriteAsyncCore(object value, Type type, Stream utf8Jso { flushThreshold = (int)(bufferWriter.Capacity * .9); //todo: determine best value here - isFinalBlock = Write(ref writerState, bufferWriter, flushThreshold, options, ref state); + isFinalBlock = Write(writer, flushThreshold, options, ref state); + writer.Flush(); + #if BUILDING_INBOX_LIBRARY await utf8Json.WriteAsync(bufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); #else diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs index 0b6416340752..1913e739a8af 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Write.cs @@ -9,33 +9,12 @@ namespace System.Text.Json.Serialization { public static partial class JsonSerializer { - private static bool Write( - ref JsonWriterState writerState, - IBufferWriter bufferWriter, - int flushThreshold, - JsonSerializerOptions options, - ref WriteStack state) - { - Utf8JsonWriter writer = new Utf8JsonWriter(bufferWriter, writerState); - - bool isFinalBlock = Write( - ref writer, - flushThreshold, - options, - ref state); - - writer.Flush(isFinalBlock: isFinalBlock); - writerState = writer.GetCurrentState(); - - return isFinalBlock; - } - // There are three conditions to consider for an object (primitive value, enumerable or object) being processed here: // 1) The object type was specified as the root-level return type to a Parse\Read method. // 2) The object is property on a parent object. // 3) The object is an element in an enumerable. private static bool Write( - ref Utf8JsonWriter writer, + Utf8JsonWriter writer, int flushThreshold, JsonSerializerOptions options, ref WriteStack state) @@ -44,26 +23,29 @@ private static bool Write( bool finishedSerializing; do { - switch (state.Current.JsonClassInfo.ClassType) + WriteStackFrame current = state.Current; + switch (current.JsonClassInfo.ClassType) { case ClassType.Enumerable: - finishedSerializing = WriteEnumerable(options, ref writer, ref state); + finishedSerializing = HandleEnumerable(current.JsonClassInfo.ElementClassInfo, options, writer, ref state); break; case ClassType.Value: - finishedSerializing = WriteValue(options, ref writer, ref state.Current); + Debug.Assert(current.JsonPropertyInfo.ClassType == ClassType.Value); + current.JsonPropertyInfo.Write(options, ref current, writer); + finishedSerializing = true; break; case ClassType.Object: - finishedSerializing = WriteObject(options, ref writer, ref state); + finishedSerializing = WriteObject(options, writer, ref state); break; default: Debug.Assert(state.Current.JsonClassInfo.ClassType == ClassType.Unknown); // Treat typeof(object) as an empty object. - finishedSerializing = WriteObject(options, ref writer, ref state); + finishedSerializing = WriteObject(options, writer, ref state); break; } - if (flushThreshold >= 0 && writer.BytesWritten > flushThreshold) + if (flushThreshold >= 0 && writer.BytesPending > flushThreshold) { return false; } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs index e8230a8696ea..34129b53c4be 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializerOptions.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Concurrent; +using System.Diagnostics; namespace System.Text.Json.Serialization { @@ -13,43 +14,56 @@ public sealed class JsonSerializerOptions { internal const int BufferSizeDefault = 16 * 1024; + internal static readonly JsonSerializerOptions s_defaultOptions = new JsonSerializerOptions(); + + private readonly ConcurrentDictionary _classes = new ConcurrentDictionary(); private ClassMaterializer _classMaterializerStrategy; + private JsonNamingPolicy _dictionayKeyPolicy; + private JsonNamingPolicy _jsonPropertyNamingPolicy; + private JsonCommentHandling _readCommentHandling; private int _defaultBufferSize = BufferSizeDefault; - - private static readonly ConcurrentDictionary s_classes = new ConcurrentDictionary(); + private int _maxDepth; + private bool _allowTrailingCommas; + private bool _haveTypesBeenCreated; + private bool _ignoreNullValues; + private bool _ignoreReadOnlyProperties; + private bool _propertyNameCaseInsensitive; + private bool _writeIndented; /// /// Constructs a new instance. /// public JsonSerializerOptions() { } - internal JsonClassInfo GetOrAddClass(Type classType) + /// + /// Defines whether an extra comma at the end of a list of JSON values in an object or array + /// is allowed (and ignored) within the JSON payload being deserialized. + /// By default, it's set to false, and is thrown if a trailing comma is encountered. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool AllowTrailingCommas { - JsonClassInfo result; - - if (!s_classes.TryGetValue(classType, out result)) + get { - result = s_classes.GetOrAdd(classType, new JsonClassInfo(classType, this)); + return _allowTrailingCommas; + } + set + { + VerifyMutable(); + _allowTrailingCommas = value; } - - return result; } - /// - /// Options to control the . - /// - public JsonReaderOptions ReaderOptions { get; set; } - - /// - /// Options to control the . - /// - public JsonWriterOptions WriterOptions { get; set; } - /// /// The default buffer size in bytes used when creating temporary buffers. /// /// The default size is 16K. /// Thrown when the buffer size is less than 1. + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// public int DefaultBufferSize { get @@ -58,6 +72,8 @@ public int DefaultBufferSize } set { + VerifyMutable(); + if (value < 1) { throw new ArgumentException(SR.SerializationInvalidBufferSize); @@ -68,14 +84,165 @@ public int DefaultBufferSize } /// - /// Determines whether null values of properties are ignored or whether they are written to the JSON. + /// Specifies the policy used to convert a key's name to another format, such as camel-casing. + /// + /// + /// This property can be set to to specify a camel-casing policy. + /// + public JsonNamingPolicy DictionaryKeyPolicy + { + get + { + return _dictionayKeyPolicy; + } + set + { + VerifyMutable(); + _dictionayKeyPolicy = value; + } + } + + /// + /// Determines whether null values are ignored during serialization and deserialization. + /// The default value is false. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool IgnoreNullValues + { + get + { + return _ignoreNullValues; + } + set + { + VerifyMutable(); + _ignoreNullValues = value; + } + } + + /// + /// Determines whether read-only properties are ignored during serialization and deserialization. + /// A property is read-only if it contains a public getter but not a public setter. + /// The default value is false. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool IgnoreReadOnlyProperties + { + get + { + return _ignoreReadOnlyProperties; + } + set + { + VerifyMutable(); + _ignoreReadOnlyProperties = value; + } + } + + /// + /// Gets or sets the maximum depth allowed when serializing or deserializing JSON, with the default (i.e. 0) indicating a max depth of 64. + /// Going past this depth will throw a . + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public int MaxDepth + { + get + { + return _maxDepth; + } + set + { + VerifyMutable(); + _maxDepth = value; + } + } + + /// + /// Specifies the policy used to convert a property's name on an object to another format, such as camel-casing. + /// The resulting property name is expected to match the JSON payload during deserialization, and + /// will be used when writing the property name during serialization. + /// + /// + /// The policy is not used for properties that have a applied. + /// This property can be set to to specify a camel-casing policy. + /// + public JsonNamingPolicy PropertyNamingPolicy + { + get + { + return _jsonPropertyNamingPolicy; + } + set + { + VerifyMutable(); + _jsonPropertyNamingPolicy = value; + } + } + + /// + /// Determines whether a property's name uses a case-insensitive comparison during deserialization. + /// The default value is false. /// - public bool IgnoreNullPropertyValueOnWrite { get; set; } + /// There is a performance cost associated when the value is true. + public bool PropertyNameCaseInsensitive + { + get + { + return _propertyNameCaseInsensitive; + } + set + { + VerifyMutable(); + _propertyNameCaseInsensitive = value; + } + } + + /// + /// Defines how the comments are handled during deserialization. + /// By default is thrown if a comment is encountered. + /// + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public JsonCommentHandling ReadCommentHandling + { + get + { + return _readCommentHandling; + } + set + { + VerifyMutable(); + _readCommentHandling = value; + } + } /// - /// Determines whether null values in the JSON are ignored or whether they are set on properties. + /// Defines whether JSON should pretty print which includes: + /// indenting nested JSON tokens, adding new lines, and adding white space between property names and values. + /// By default, the JSON is serialized without any extra white space. /// - public bool IgnoreNullPropertyValueOnRead { get; set; } + /// + /// Thrown if this property is set after serialization or deserialization has occurred. + /// + public bool WriteIndented + { + get + { + return _writeIndented; + } + set + { + VerifyMutable(); + _writeIndented = value; + } + } internal ClassMaterializer ClassMaterializerStrategy { @@ -94,5 +261,47 @@ internal ClassMaterializer ClassMaterializerStrategy return _classMaterializerStrategy; } } + + internal JsonClassInfo GetOrAddClass(Type classType) + { + _haveTypesBeenCreated = true; + + // todo: for performance, consider obtaining the type from s_defaultOptions and then cloning. + if (!_classes.TryGetValue(classType, out JsonClassInfo result)) + { + result = _classes.GetOrAdd(classType, new JsonClassInfo(classType, this)); + } + + return result; + } + + internal JsonReaderOptions GetReaderOptions() + { + return new JsonReaderOptions + { + AllowTrailingCommas = AllowTrailingCommas, + CommentHandling = ReadCommentHandling, + MaxDepth = MaxDepth + }; + } + + internal JsonWriterOptions GetWriterOptions() + { + return new JsonWriterOptions + { + Indented = WriteIndented + }; + } + + private void VerifyMutable() + { + // The default options are hidden and thus should be immutable. + Debug.Assert(this != s_defaultOptions); + + if (_haveTypesBeenCreated) + { + ThrowHelper.ThrowInvalidOperationException_SerializerOptionsImmutable(); + } + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs index 35d958666a56..3598e344281d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/Policies/JsonValueConverter.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.Serialization.Policies internal abstract class JsonValueConverter { public abstract bool TryRead(Type valueType, ref Utf8JsonReader reader, out TValue value); - public abstract void Write(TValue value, ref Utf8JsonWriter writer); - public abstract void Write(Span escapedPropertyName, TValue value, ref Utf8JsonWriter writer); + public abstract void Write(TValue value, Utf8JsonWriter writer); + public abstract void Write(Span escapedPropertyName, TValue value, Utf8JsonWriter writer); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs similarity index 93% rename from src/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs rename to src/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs index 7524f36b2cf8..b3f189464db5 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ArrayBufferWriter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/PooledBufferWriter.cs @@ -7,21 +7,23 @@ namespace System.Text.Json.Serialization { - // Note: this is currently an internal class that will be replaced with a shared version. - internal sealed class ArrayBufferWriter : IBufferWriter, IDisposable + /// + /// This is an implementation detail and MUST NOT be called by source-package consumers. + /// + internal sealed class PooledBufferWriter : IBufferWriter, IDisposable { private T[] _rentedBuffer; private int _index; private const int MinimumBufferSize = 256; - public ArrayBufferWriter() + public PooledBufferWriter() { _rentedBuffer = ArrayPool.Shared.Rent(MinimumBufferSize); _index = 0; } - public ArrayBufferWriter(int initialCapacity) + public PooledBufferWriter(int initialCapacity) { if (initialCapacity <= 0) throw new ArgumentException(nameof(initialCapacity)); @@ -101,7 +103,7 @@ public void Dispose() private void CheckIfDisposed() { if (_rentedBuffer == null) - ThrowHelper.ThrowObjectDisposedException(nameof(ArrayBufferWriter)); + ThrowHelper.ThrowObjectDisposedException(nameof(PooledBufferWriter)); } public void Advance(int count) diff --git a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs index be230e920926..ee8cce374983 100644 --- a/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs +++ b/src/System.Text.Json/src/System/Text/Json/Serialization/ReadStackFrame.cs @@ -20,7 +20,7 @@ internal struct ReadStackFrame internal bool EnumerableCreated; // Support System.Array and other types that don't implement IList - internal List TempEnumerableValues; + internal IList TempEnumerableValues; // For performance, we order the properties by the first deserialize and PropertyIndex helps find the right slot quicker. internal int PropertyIndex; @@ -93,10 +93,23 @@ public Type GetElementType() internal static object CreateEnumerableValue(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options) { + JsonPropertyInfo jsonPropertyInfo = state.Current.JsonPropertyInfo; + // If the property has an EnumerableConverter, then we use tempEnumerableValues. - if (state.Current.JsonPropertyInfo.EnumerableConverter != null) + if (jsonPropertyInfo.EnumerableConverter != null) { - state.Current.TempEnumerableValues = new List(); + IList converterList; + if (jsonPropertyInfo.ElementClassInfo.ClassType == ClassType.Value) + { + converterList = jsonPropertyInfo.ElementClassInfo.GetPolicyProperty().CreateConverterList(); + } + else + { + converterList = new List(); + } + + state.Current.TempEnumerableValues = converterList; + return null; } @@ -134,38 +147,5 @@ internal void SetReturnValue(object value, JsonSerializerOptions options) Debug.Assert(ReturnValue == null); ReturnValue = value; } - - internal static void SetReturnValue(object value, JsonSerializerOptions options, ref ReadStackFrame current, bool setPropertyDirectly = false) - { - if (current.IsEnumerable()) - { - if (current.TempEnumerableValues != null) - { - current.TempEnumerableValues.Add(value); - } - else - { - ((IList)current.ReturnValue).Add(value); - } - } - else if (!setPropertyDirectly && current.IsPropertyEnumerable()) - { - Debug.Assert(current.JsonPropertyInfo != null); - Debug.Assert(current.ReturnValue != null); - if (current.TempEnumerableValues != null) - { - current.TempEnumerableValues.Add(value); - } - else - { - ((IList)current.JsonPropertyInfo.GetValueAsObject(current.ReturnValue, options)).Add(value); - } - } - else - { - Debug.Assert(current.JsonPropertyInfo != null); - current.JsonPropertyInfo.SetValueAsObject(current.ReturnValue, value, options); - } - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs index 6bd5fc1150ee..ac7f1f3d8d2a 100644 --- a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs +++ b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.Serialization.cs @@ -2,6 +2,7 @@ // 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.Reflection; using System.Runtime.CompilerServices; using System.Text.Json.Serialization; @@ -18,13 +19,7 @@ public static void ThrowArgumentException_DeserializeWrongType(Type type, object [MethodImpl(MethodImplOptions.NoInlining)] public static void ThrowJsonReaderException_DeserializeUnableToConvertValue(Type propertyType, in Utf8JsonReader reader, in ReadStack state) { - throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType), reader.CurrentState); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowJsonReaderException_DeserializeUnableToConvertValue(Type propertyType, in ReadStack state) - { - throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType), 0 , 0); + throw new JsonReaderException(SR.Format(SR.DeserializeUnableToConvertValue, state.PropertyPath, propertyType.FullName), reader.CurrentState); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -38,5 +33,23 @@ public static void ThrowObjectDisposedException(string name) { throw new ObjectDisposedException(name); } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException_SerializerOptionsImmutable() + { + throw new InvalidOperationException(SR.SerializerOptionsImmutable); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException_SerializerPropertyNameConflict(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo) + { + throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameConflict, jsonClassInfo.Type.FullName, jsonPropertyInfo.PropertyInfo.Name)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowInvalidOperationException_SerializerPropertyNameNull(JsonClassInfo jsonClassInfo, JsonPropertyInfo jsonPropertyInfo) + { + throw new InvalidOperationException(SR.Format(SR.SerializerPropertyNameNull, jsonClassInfo.Type.FullName, jsonPropertyInfo.PropertyInfo.Name)); + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.cs b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.cs index 3ba9fec9e841..4d9abbd804d5 100644 --- a/src/System.Text.Json/src/System/Text/Json/ThrowHelper.cs +++ b/src/System.Text.Json/src/System/Text/Json/ThrowHelper.cs @@ -45,17 +45,9 @@ public static void ThrowArgumentException_ValueNotSupported() throw GetArgumentException(SR.SpecialNumberValuesNotSupported); } - public static void ThrowArgumentException(ExceptionResource resource, int minimumSize = 0) + public static void ThrowInvalidOperationException_NeedLargerSpan() { - if (resource == ExceptionResource.FailedToGetLargerSpan) - { - throw GetArgumentException(SR.FailedToGetLargerSpan); - } - else - { - Debug.Assert(resource == ExceptionResource.FailedToGetMinimumSizeSpan); - throw GetArgumentException(SR.Format(SR.FailedToGetMinimumSizeSpan, minimumSize)); - } + throw GetInvalidOperationException(SR.FailedToGetLargerSpan); } public static void ThrowArgumentException(ReadOnlySpan propertyName, ReadOnlySpan value) @@ -258,6 +250,12 @@ private static string GetResourceString(ref Utf8JsonReader json, ExceptionResour case ExceptionResource.MismatchedObjectArray: message = SR.Format(SR.MismatchedObjectArray, character); break; + case ExceptionResource.TrailingCommaNotAllowedBeforeArrayEnd: + message = SR.TrailingCommaNotAllowedBeforeArrayEnd; + break; + case ExceptionResource.TrailingCommaNotAllowedBeforeObjectEnd: + message = SR.TrailingCommaNotAllowedBeforeObjectEnd; + break; case ExceptionResource.EndOfStringNotFound: message = SR.EndOfStringNotFound; break; @@ -288,6 +286,9 @@ private static string GetResourceString(ref Utf8JsonReader json, ExceptionResour case ExceptionResource.ExpectedStartOfPropertyOrValueNotFound: message = SR.ExpectedStartOfPropertyOrValueNotFound; break; + case ExceptionResource.ExpectedStartOfPropertyOrValueAfterComment: + message = SR.Format(SR.ExpectedStartOfPropertyOrValueAfterComment, character); + break; case ExceptionResource.ExpectedStartOfValueNotFound: message = SR.Format(SR.ExpectedStartOfValueNotFound, character); break; @@ -343,6 +344,11 @@ public static void ThrowInvalidOperationException(ExceptionResource resource, in throw GetInvalidOperationException(resource, currentDepth, token, tokenType); } + public static void ThrowArgumentException_InvalidCommentValue() + { + throw new ArgumentException(SR.CannotWriteCommentWithEmbeddedDelimiter); + } + public static void ThrowArgumentException_InvalidUTF8(ReadOnlySpan value) { var builder = new StringBuilder(); @@ -506,6 +512,7 @@ internal enum ExceptionResource ExpectedSeparatorAfterPropertyNameNotFound, ExpectedStartOfPropertyNotFound, ExpectedStartOfPropertyOrValueNotFound, + ExpectedStartOfPropertyOrValueAfterComment, ExpectedStartOfValueNotFound, ExpectedTrue, ExpectedValueAfterPropertyNameNotFound, @@ -522,10 +529,10 @@ internal enum ExceptionResource CannotStartObjectArrayAfterPrimitiveOrClose, CannotWriteValueWithinObject, CannotWriteValueAfterPrimitive, - FailedToGetMinimumSizeSpan, - FailedToGetLargerSpan, CannotWritePropertyWithinArray, - ExpectedJsonTokens + ExpectedJsonTokens, + TrailingCommaNotAllowedBeforeArrayEnd, + TrailingCommaNotAllowedBeforeObjectEnd, } internal enum NumericType diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs b/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs index c87795c0dbe6..82b67a43678c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterHelper.cs @@ -9,40 +9,25 @@ namespace System.Text.Json { internal static partial class JsonWriterHelper { - public static bool TryWriteIndentation(Span buffer, int indent, out int bytesWritten) + public static void WriteIndentation(Span buffer, int indent) { Debug.Assert(indent % JsonConstants.SpacesPerIndent == 0); + Debug.Assert(buffer.Length >= indent); - if (buffer.Length >= indent) - { - // Based on perf tests, the break-even point where vectorized Fill is faster - // than explicitly writing the space in a loop is 8. - if (indent < 8) - { - int i = 0; - while (i < indent) - { - buffer[i++] = JsonConstants.Space; - buffer[i++] = JsonConstants.Space; - } - } - else - { - buffer.Slice(0, indent).Fill(JsonConstants.Space); - } - bytesWritten = indent; - return true; - } - else + // Based on perf tests, the break-even point where vectorized Fill is faster + // than explicitly writing the space in a loop is 8. + if (indent < 8) { int i = 0; - while (i < buffer.Length - 1) + while (i < indent) { buffer[i++] = JsonConstants.Space; buffer[i++] = JsonConstants.Space; } - bytesWritten = i; - return false; + } + else + { + buffer.Slice(0, indent).Fill(JsonConstants.Space); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs b/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs deleted file mode 100644 index 52e98d4a3f7d..000000000000 --- a/src/System.Text.Json/src/System/Text/Json/Writer/JsonWriterState.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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. - -namespace System.Text.Json -{ - /// - /// Defines an opaque type that holds and saves all the relevant state information which must be provided - /// to the to continue writing after completing a partial write. - /// - /// - /// This type is required to support reentrancy when writing incomplete data, and to continue - /// writing in chunks. Unlike the , which is a ref struct, - /// this type can survive across async/await boundaries and hence this type is required to provide - /// support for writing more JSON text asynchronously before continuing with a new instance of the . - /// - public struct JsonWriterState - { - internal long _bytesWritten; - internal long _bytesCommitted; - internal bool _inObject; - internal bool _isNotPrimitive; - internal JsonTokenType _tokenType; - internal int _currentDepth; - internal JsonWriterOptions _writerOptions; - internal BitStack _bitStack; - - /// - /// Returns the total amount of bytes written by the so far. - /// This includes data that has been written beyond what has already been committed. - /// - public long BytesWritten => _bytesWritten; - - /// - /// Returns the total amount of bytes committed to the output by the so far. - /// This is how much the IBufferWriter has advanced. - /// - public long BytesCommitted => _bytesCommitted; - - /// - /// Constructs a new instance. - /// - /// Defines the customized behavior of the - /// By default, the writes JSON minimized (i.e. with no extra whitespace) - /// and validates that the JSON being written is structurally valid according to JSON RFC. - /// - /// An instance of this state must be passed to the ctor with the output destination. - /// Unlike the , which is a ref struct, the state can survive - /// across async/await boundaries and hence this type is required to provide support for reading - /// in more data asynchronously before continuing with a new instance of the . - /// - public JsonWriterState(JsonWriterOptions options = default) - { - _bytesWritten = default; - _bytesCommitted = default; - _inObject = default; - _isNotPrimitive = default; - _tokenType = default; - _currentDepth = default; - _writerOptions = options; - - // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. - // This way we avoid allocations in the common, default cases, and allocate lazily. - _bitStack = default; - } - - /// - /// Gets the custom behavior when writing JSON using - /// the which indicates whether to format the output - /// while writing and whether to skip structural JSON validation or not. - /// - public JsonWriterOptions Options => _writerOptions; - } -} diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs index de9873babb0d..065c72f982a1 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTime.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(string propertyName, DateTime value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, DateTime value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ public void WriteString(string propertyName, DateTime value, bool escape = true) /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(ReadOnlySpan propertyName, DateTime value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, DateTime value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ public void WriteString(ReadOnlySpan propertyName, DateTime value, bool es /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ public void WriteString(ReadOnlySpan propertyName, DateTime value, bool es /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000. /// - public void WriteString(ReadOnlySpan utf8PropertyName, DateTime value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, DateTime value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ private void WriteStringEscape(ReadOnlySpan propertyName, DateTime value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ private void WriteStringEscape(ReadOnlySpan utf8PropertyName, DateTime val { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan propertyName, DateTime char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Date byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Date private void WriteStringByOptions(ReadOnlySpan propertyName, DateTime value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ private void WriteStringByOptions(ReadOnlySpan propertyName, DateTime valu private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTime value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,76 +190,180 @@ private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTime private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTime value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTime value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); - WriteStringValue(value, ref idx); + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - Advance(idx); - } + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); - } + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(DateTime value, ref int idx) + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(DateTime value, ref int idx) - { - Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); Debug.Assert(result); - JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } + + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTime value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); - if (_buffer.Length - idx < bytesWritten) + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, bytesWritten); + Grow(maxRequired); } - tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx)); + Span output = _memory.Span; - idx += bytesWritten; - } + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O'); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs index 95cb8572efb4..02474bdd7018 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.DateTimeOffset.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(string propertyName, DateTimeOffset value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, DateTimeOffset value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ public void WriteString(string propertyName, DateTimeOffset value, bool escape = /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value, b /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ public void WriteString(ReadOnlySpan propertyName, DateTimeOffset value, b /// /// Writes the using the round-trippable ('O') , for example: 2017-06-12T05:30:45.7680000-07:00. /// - public void WriteString(ReadOnlySpan utf8PropertyName, DateTimeOffset value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, DateTimeOffset value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ private void WriteStringEscape(ReadOnlySpan propertyName, DateTimeOffset v { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ private void WriteStringEscape(ReadOnlySpan utf8PropertyName, DateTimeOffs { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan propertyName, DateTime char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Date byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Date private void WriteStringByOptions(ReadOnlySpan propertyName, DateTimeOffset value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ private void WriteStringByOptions(ReadOnlySpan propertyName, DateTimeOffse private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTimeOffset value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,74 +190,180 @@ private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, DateTimeO private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDateTimeOffsetLength - 6); - WriteStringValue(value, ref idx); + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - Advance(idx); - } + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); - } + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(DateTimeOffset value, ref int idx) + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDateTimeOffsetLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(DateTimeOffset value, ref int idx) - { - Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); Debug.Assert(result); - JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; + } + + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, DateTimeOffset value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDateTimeOffsetLength - 7 - s_newLineLength); + + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDateTimeOffsetLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - if (_buffer.Length - idx < bytesWritten) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx, bytesWritten); + output[BytesPending++] = JsonConstants.ListSeparator; } - tempSpan.Slice(0, bytesWritten).CopyTo(_buffer.Slice(idx)); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; - idx += bytesWritten; + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs index f03e84d07d11..be3a8f5248ac 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Decimal.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, decimal value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, decimal value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ public void WriteNumber(string propertyName, decimal value, bool escape = true) /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, decimal value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, decimal value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -65,7 +62,9 @@ public void WriteNumber(ReadOnlySpan propertyName, decimal value, bool esc /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ public void WriteNumber(ReadOnlySpan propertyName, decimal value, bool esc /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, decimal value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, decimal value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -96,7 +88,7 @@ private void WriteNumberEscape(ReadOnlySpan propertyName, decimal value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, decimal valu { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, decimal char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, deci byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, deci private void WriteNumberByOptions(ReadOnlySpan propertyName, decimal value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -206,7 +178,7 @@ private void WriteNumberByOptions(ReadOnlySpan propertyName, decimal value private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, decimal value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -218,49 +190,152 @@ private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, decimal v private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDecimalLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDecimalLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteNumberValueFormatLoop(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); - Advance(idx); + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDecimalLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, decimal value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDecimalLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDecimalLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(decimal value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDecimalLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs index ea19ce3a838c..c8c67ad3c77e 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Double.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, double value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, double value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,19 +47,12 @@ public void WriteNumber(string propertyName, double value, bool escape = true) /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, double value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, double value) { JsonWriterHelper.ValidateProperty(propertyName); JsonWriterHelper.ValidateDouble(value); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -66,7 +63,9 @@ public void WriteNumber(ReadOnlySpan propertyName, double value, bool esca /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -76,19 +75,12 @@ public void WriteNumber(ReadOnlySpan propertyName, double value, bool esca /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, double value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, double value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); JsonWriterHelper.ValidateDouble(value); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -98,7 +90,7 @@ private void WriteNumberEscape(ReadOnlySpan propertyName, double value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -114,7 +106,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, double value { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -134,21 +126,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, double v char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -167,21 +149,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, doub byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -195,7 +167,7 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, doub private void WriteNumberByOptions(ReadOnlySpan propertyName, double value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -208,7 +180,7 @@ private void WriteNumberByOptions(ReadOnlySpan propertyName, double value) private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, double value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -220,49 +192,152 @@ private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, double va private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatDoubleLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatDoubleLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteNumberValueFormatLoop(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); - Advance(idx); + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatDoubleLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, double value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatDoubleLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatDoubleLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(double value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatDoubleLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs index 5853265c4a68..7595cd4d8f82 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Float.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(string propertyName, float value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, float value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,19 +47,12 @@ public void WriteNumber(string propertyName, float value, bool escape = true) /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan propertyName, float value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, float value) { JsonWriterHelper.ValidateProperty(propertyName); JsonWriterHelper.ValidateSingle(value); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -66,7 +63,9 @@ public void WriteNumber(ReadOnlySpan propertyName, float value, bool escap /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -76,19 +75,12 @@ public void WriteNumber(ReadOnlySpan propertyName, float value, bool escap /// /// Writes the using the default (i.e. 'G'). /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, float value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, float value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); JsonWriterHelper.ValidateSingle(value); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -98,7 +90,7 @@ private void WriteNumberEscape(ReadOnlySpan propertyName, float value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -114,7 +106,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, float value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -134,21 +126,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, float va char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -167,21 +149,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, floa byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -195,7 +167,7 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, floa private void WriteNumberByOptions(ReadOnlySpan propertyName, float value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -205,64 +177,167 @@ private void WriteNumberByOptions(ReadOnlySpan propertyName, float value) } } - private void WriteNumberByOptions(ReadOnlySpan propertyName, float value) + private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, float value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(propertyName, value); + WriteNumberIndented(utf8PropertyName, value); } else { - WriteNumberMinimized(propertyName, value); + WriteNumberMinimized(utf8PropertyName, value); } } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatSingleLength - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteNumberValueFormatLoop(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatSingleLength - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatSingleLength + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - WriteNumberValueFormatLoop(value, ref idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, float value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatSingleLength - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatSingleLength + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(float value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatSingleLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + WriteNewLine(output); } - idx += bytesWritten; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs index 23233e0de721..f7af6fc4a7be 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.FormattedNumber.cs @@ -3,18 +3,20 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Buffers.Text; using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -30,9 +32,11 @@ public ref partial struct Utf8JsonWriter internal void WriteNumber(ReadOnlySpan propertyName, ReadOnlySpan utf8FormattedNumber) { JsonWriterHelper.ValidateProperty(propertyName); + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); WriteNumberEscape(propertyName, utf8FormattedNumber); + SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; } @@ -42,6 +46,9 @@ internal void WriteNumber(ReadOnlySpan propertyName, ReadOnlySpan ut /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -57,9 +64,11 @@ internal void WriteNumber(ReadOnlySpan propertyName, ReadOnlySpan ut internal void WriteNumber(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8FormattedNumber) { JsonWriterHelper.ValidateProperty(utf8PropertyName); + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); WriteNumberEscape(utf8PropertyName, utf8FormattedNumber); + SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; } @@ -68,7 +77,7 @@ private void WriteNumberEscape(ReadOnlySpan propertyName, ReadOnlySpan= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -84,7 +93,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, ReadOnlySpan { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -104,21 +113,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, ReadOnly char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -137,21 +136,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, Read byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,74 +154,27 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, Read private void WriteNumberByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(propertyName, value); + WriteLiteralIndented(propertyName, value); } else { - WriteNumberMinimized(propertyName, value); + WriteLiteralMinimized(propertyName, value); } } private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { - WriteNumberIndented(utf8PropertyName, value); + WriteLiteralIndented(utf8PropertyName, value); } else { - WriteNumberMinimized(utf8PropertyName, value); - } - } - - private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameMinimized(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameMinimized(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int idx = WritePropertyNameIndented(escapedPropertyName); - - WriteNumberValueFormatLoop(value, ref idx); - - Advance(idx); - } - - private void WriteNumberValueFormatLoop(ReadOnlySpan value, ref int idx) - { - if (_buffer.Length - idx - value.Length < 0) - { - AdvanceAndGrow(ref idx, value.Length); + WriteLiteralMinimized(utf8PropertyName, value); } - - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs index c201b6afa1b3..d1e4c6924a9c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Guid.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(string propertyName, Guid value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, Guid value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ public void WriteString(string propertyName, Guid value, bool escape = true) /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(ReadOnlySpan propertyName, Guid value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, Guid value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringByOptions(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -65,7 +62,9 @@ public void WriteString(ReadOnlySpan propertyName, Guid value, bool escape /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ public void WriteString(ReadOnlySpan propertyName, Guid value, bool escape /// /// Writes the using the default (i.e. 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn. /// - public void WriteString(ReadOnlySpan utf8PropertyName, Guid value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, Guid value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringByOptions(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -96,7 +88,7 @@ private void WriteStringEscape(ReadOnlySpan propertyName, Guid value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -112,7 +104,7 @@ private void WriteStringEscape(ReadOnlySpan utf8PropertyName, Guid value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -132,21 +124,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan propertyName, Guid val char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -165,21 +147,11 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Guid byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStringByOptions(escapedPropertyName.Slice(0, written), value); @@ -193,7 +165,7 @@ private void WriteStringEscapeProperty(ReadOnlySpan utf8PropertyName, Guid private void WriteStringByOptions(ReadOnlySpan propertyName, Guid value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -206,7 +178,7 @@ private void WriteStringByOptions(ReadOnlySpan propertyName, Guid value) private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, Guid value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -218,66 +190,168 @@ private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, Guid valu private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatGuidLength - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for date, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatGuidLength - 6); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 5; // 2 quotes for property name, 2 quotes for date, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(value, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatGuidLength + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringIndented(ReadOnlySpan escapedPropertyName, Guid value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteStringValue(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatGuidLength - 7 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatGuidLength + 6; // 2 quotes for property name, 2 quotes for date, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteStringValue(Guid value, ref int idx) - { - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - FormatLoop(value, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - } - private void FormatLoop(Guid value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatGuidLength); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + WriteNewLine(output); } - idx += bytesWritten; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs index 9c1867c74a23..4d6f04df9feb 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Helpers.cs @@ -9,7 +9,7 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidatePropertyNameAndDepth(ReadOnlySpan propertyName) @@ -28,7 +28,7 @@ private void ValidatePropertyNameAndDepth(ReadOnlySpan utf8PropertyName) [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidateWritingProperty() { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (!_inObject) { @@ -41,7 +41,7 @@ private void ValidateWritingProperty() [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ValidateWritingProperty(byte token) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (!_inObject) { @@ -52,212 +52,154 @@ private void ValidateWritingProperty(byte token) } } - private int WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + Debug.Assert(escapedPropertyName.Length < int.MaxValue - 5); - if (_buffer.Length <= idx) + int minRequired = escapedPropertyName.Length + 4; // 2 quotes, 1 colon, and 1 start token + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedPropertyName, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.Quote; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.KeyValueSeperator; + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - return idx; + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = token; } - private int WritePropertyNameIndented(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - 6 - s_newLineLength); - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } + int minRequired = indent + escapedPropertyName.Length + 5; // 2 quotes, 1 colon, 1 space, and 1 start token + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedPropertyName, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.KeyValueSeperator; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Space; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - return idx; + output[BytesPending++] = JsonConstants.Quote; + + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + output[BytesPending++] = token; } - private int WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameMinimized(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 5); - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Quote; + // All ASCII, 2 quotes, 1 colon, and 1 start token => escapedPropertyName.Length + 4 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 5; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); - int partialConsumed = 0; - while (true) + if (_memory.Length - BytesPending < maxRequired) { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Quote; + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Quote; - return idx; + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = token; } - private int WritePropertyNameIndented(ReadOnlySpan escapedPropertyName) + private void WritePropertyNameIndented(ReadOnlySpan escapedPropertyName, byte token) { - int idx = 0; - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 6 - s_newLineLength); - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } + // All ASCII, 2 quotes, 1 colon, 1 space, and 1 start token => indent + escapedPropertyName.Length + 5 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6 + s_newLineLength; - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); - int partialConsumed = 0; - while (true) - { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); - } + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.KeyValueSeperator; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Space; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - return idx; + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + output[BytesPending++] = token; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void TranscodeAndWrite(ReadOnlySpan escapedPropertyName, Span output) + { + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedPropertyName); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int consumed, out int written); + Debug.Assert(status == OperationStatus.Done); + Debug.Assert(consumed == byteSpan.Length); + BytesPending += written; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs index 13f3a1671aca..f5e2a1469d95 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.Literal.cs @@ -7,47 +7,44 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(string propertyName, bool escape = true) - => WriteNull(propertyName.AsSpan(), escape); + public void WriteNull(string propertyName) + => WriteNull(propertyName.AsSpan()); /// /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(ReadOnlySpan propertyName, bool escape = true) + public void WriteNull(ReadOnlySpan propertyName) { JsonWriterHelper.ValidateProperty(propertyName); ReadOnlySpan span = JsonConstants.NullValue; - if (escape) - { - WriteLiteralEscape(propertyName, span); - } - else - { - WriteLiteralByOptions(propertyName, span); - } + WriteLiteralEscape(propertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Null; @@ -57,27 +54,22 @@ public void WriteNull(ReadOnlySpan propertyName, bool escape = true) /// Writes the property name and the JSON literal "null" as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteNull(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteNull(ReadOnlySpan utf8PropertyName) { JsonWriterHelper.ValidateProperty(utf8PropertyName); ReadOnlySpan span = JsonConstants.NullValue; - if (escape) - { - WriteLiteralEscape(utf8PropertyName, span); - } - else - { - WriteLiteralByOptions(utf8PropertyName, span); - } + WriteLiteralEscape(utf8PropertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Null; @@ -88,42 +80,39 @@ public void WriteNull(ReadOnlySpan utf8PropertyName, bool escape = true) /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(string propertyName, bool value, bool escape = true) - => WriteBoolean(propertyName.AsSpan(), value, escape); + public void WriteBoolean(string propertyName, bool value) + => WriteBoolean(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON literal "true" or "false") as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(ReadOnlySpan propertyName, bool value, bool escape = true) + public void WriteBoolean(ReadOnlySpan propertyName, bool value) { JsonWriterHelper.ValidateProperty(propertyName); ReadOnlySpan span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue; - if (escape) - { - WriteLiteralEscape(propertyName, span); - } - else - { - WriteLiteralByOptions(propertyName, span); - } + WriteLiteralEscape(propertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = value ? JsonTokenType.True : JsonTokenType.False; @@ -134,27 +123,22 @@ public void WriteBoolean(ReadOnlySpan propertyName, bool value, bool escap /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON literal "true" or "false" as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteBoolean(ReadOnlySpan utf8PropertyName, bool value, bool escape = true) + public void WriteBoolean(ReadOnlySpan utf8PropertyName, bool value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); ReadOnlySpan span = value ? JsonConstants.TrueValue : JsonConstants.FalseValue; - if (escape) - { - WriteLiteralEscape(utf8PropertyName, span); - } - else - { - WriteLiteralByOptions(utf8PropertyName, span); - } + WriteLiteralEscape(utf8PropertyName, span); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = value ? JsonTokenType.True : JsonTokenType.False; @@ -164,7 +148,7 @@ private void WriteLiteralEscape(ReadOnlySpan propertyName, ReadOnlySpan= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -180,7 +164,7 @@ private void WriteLiteralEscape(ReadOnlySpan utf8PropertyName, ReadOnlySpa { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -200,21 +184,11 @@ private void WriteLiteralEscapeProperty(ReadOnlySpan propertyName, ReadOnl char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value); @@ -233,21 +207,11 @@ private void WriteLiteralEscapeProperty(ReadOnlySpan utf8PropertyName, Rea byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteLiteralByOptions(escapedPropertyName.Slice(0, written), value); @@ -261,49 +225,177 @@ private void WriteLiteralEscapeProperty(ReadOnlySpan utf8PropertyName, Rea private void WriteLiteralByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - int idx; - if (_writerOptions.Indented) + if (Options.Indented) + { + WriteLiteralIndented(propertyName, value); + } + else + { + WriteLiteralMinimized(propertyName, value); + } + } + + private void WriteLiteralByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) + { + ValidateWritingProperty(); + if (Options.Indented) { - idx = WritePropertyNameIndented(propertyName); + WriteLiteralIndented(utf8PropertyName, value); } else { - idx = WritePropertyNameMinimized(propertyName); + WriteLiteralMinimized(utf8PropertyName, value); + } + } + + private void WriteLiteralMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - value.Length - 4); + + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + value.Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 4; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); } - if (value.Length > _buffer.Length - idx) + Span output = _memory.Span; + + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx, value.Length); + output[BytesPending++] = JsonConstants.ListSeparator; } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; } - private void WriteLiteralByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) + private void WriteLiteralMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) { - ValidateWritingProperty(); - int idx; - if (_writerOptions.Indented) + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - value.Length - 4); + + int minRequired = escapedPropertyName.Length + value.Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) { - idx = WritePropertyNameIndented(utf8PropertyName); + Grow(maxRequired); } - else + + Span output = _memory.Span; + + if (_currentDepth < 0) { - idx = WritePropertyNameMinimized(utf8PropertyName); + output[BytesPending++] = JsonConstants.ListSeparator; } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - if (value.Length > _buffer.Length - idx) + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; + } + + private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - value.Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + value.Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + value.Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, value.Length); + Grow(maxRequired); } - value.CopyTo(_buffer.Slice(idx)); - idx += value.Length; + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; + } + + private void WriteLiteralIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) + { + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(value.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - value.Length - 5 - s_newLineLength); + + int minRequired = indent + escapedPropertyName.Length + value.Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; - Advance(idx); + value.CopyTo(output.Slice(BytesPending)); + BytesPending += value.Length; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs index be106a111daf..8ab324320f8d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.SignedNumber.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -25,15 +27,17 @@ public ref partial struct Utf8JsonWriter /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(string propertyName, long value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, long value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -43,18 +47,11 @@ public void WriteNumber(string propertyName, long value, bool escape = true) /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan propertyName, long value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, long value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -65,7 +62,9 @@ public void WriteNumber(ReadOnlySpan propertyName, long value, bool escape /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -75,18 +74,11 @@ public void WriteNumber(ReadOnlySpan propertyName, long value, bool escape /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, long value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, long value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -97,7 +89,9 @@ public void WriteNumber(ReadOnlySpan utf8PropertyName, long value, bool es /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -107,15 +101,17 @@ public void WriteNumber(ReadOnlySpan utf8PropertyName, long value, bool es /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(string propertyName, int value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), (long)value, escape); + public void WriteNumber(string propertyName, int value) + => WriteNumber(propertyName.AsSpan(), (long)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -125,15 +121,17 @@ public void WriteNumber(string propertyName, int value, bool escape = true) /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan propertyName, int value, bool escape = true) - => WriteNumber(propertyName, (long)value, escape); + public void WriteNumber(ReadOnlySpan propertyName, int value) + => WriteNumber(propertyName, (long)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -143,14 +141,14 @@ public void WriteNumber(ReadOnlySpan propertyName, int value, bool escape /// /// Writes the using the default (i.e. 'G'), for example: 32767. /// - public void WriteNumber(ReadOnlySpan utf8PropertyName, int value, bool escape = true) - => WriteNumber(utf8PropertyName, (long)value, escape); + public void WriteNumber(ReadOnlySpan utf8PropertyName, int value) + => WriteNumber(utf8PropertyName, (long)value); private void WriteNumberEscape(ReadOnlySpan propertyName, long value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -166,7 +164,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, long value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -186,21 +184,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, long val char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -219,21 +207,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, long byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -247,7 +225,7 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, long private void WriteNumberByOptions(ReadOnlySpan propertyName, long value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -260,7 +238,7 @@ private void WriteNumberByOptions(ReadOnlySpan propertyName, long value) private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, long value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -272,49 +250,152 @@ private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, long valu private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatInt64Length - 4); - WriteNumberValueFormatLoop(value, ref idx); + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 4; - Advance(idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatInt64Length - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatInt64Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, long value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatInt64Length - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(long value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatInt64Length); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs index f90c35b2e56e..10367794e8be 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.String.cs @@ -9,49 +9,44 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, string value, bool escape = true) - => WriteString(propertyName.AsSpan(), value.AsSpan(), escape); + public void WriteString(string propertyName, string value) + => WriteString(propertyName.AsSpan(), value.AsSpan()); /// /// Writes the UTF-16 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan value) { JsonWriterHelper.ValidatePropertyAndValue(propertyName, value); - if (escape) - { - WriteStringEscape(propertyName, value); - } - else - { - WriteStringDontEscape(propertyName, value); - } + WriteStringEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -62,26 +57,20 @@ public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan valu /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value) { JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, utf8Value); - if (escape) - { - WriteStringEscape(utf8PropertyName, utf8Value); - } - else - { - WriteStringDontEscape(utf8PropertyName, utf8Value); - } + WriteStringEscape(utf8PropertyName, utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -92,42 +81,37 @@ public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, ReadOnlySpan value, bool escape = true) - => WriteString(propertyName.AsSpan(), value, escape); + public void WriteString(string propertyName, ReadOnlySpan value) + => WriteString(propertyName.AsSpan(), value); /// /// Writes the UTF-8 property name and UTF-16 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan value, bool escape = true) + public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { JsonWriterHelper.ValidatePropertyAndValue(utf8PropertyName, value); - if (escape) - { - WriteStringEscape(utf8PropertyName, value); - } - else - { - WriteStringDontEscape(utf8PropertyName, value); - } + WriteStringEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -138,42 +122,37 @@ public void WriteString(ReadOnlySpan utf8PropertyName, ReadOnlySpan /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(string propertyName, ReadOnlySpan utf8Value, bool escape = true) - => WriteString(propertyName.AsSpan(), utf8Value, escape); + public void WriteString(string propertyName, ReadOnlySpan utf8Value) + => WriteString(propertyName.AsSpan(), utf8Value); /// /// Writes the UTF-16 property name and UTF-8 text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-8 encoded value to be written as a JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan utf8Value, bool escape = true) + public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan utf8Value) { JsonWriterHelper.ValidatePropertyAndValue(propertyName, utf8Value); - if (escape) - { - WriteStringEscape(propertyName, utf8Value); - } - else - { - WriteStringDontEscape(propertyName, utf8Value); - } + WriteStringEscape(propertyName, utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -184,88 +163,34 @@ public void WriteString(ReadOnlySpan propertyName, ReadOnlySpan utf8 /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan propertyName, string value, bool escape = true) - => WriteString(propertyName, value.AsSpan(), escape); + public void WriteString(ReadOnlySpan propertyName, string value) + => WriteString(propertyName, value.AsSpan()); /// /// Writes the UTF-8 property name and string text value (as a JSON string) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. - /// The value is always escaped + /// + /// The property name and value is escaped before writing. + /// /// /// Thrown when the specified property name or value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteString(ReadOnlySpan utf8PropertyName, string value, bool escape = true) - => WriteString(utf8PropertyName, value.AsSpan(), escape); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, utf8Value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, utf8Value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, utf8Value); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteStringDontEscape(ReadOnlySpan escapedPropertyName, ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - if (valueIdx != -1) - { - WriteStringEscapeValueOnly(escapedPropertyName, value, valueIdx); - } - else - { - WriteStringByOptions(escapedPropertyName, value); - } - } + public void WriteString(ReadOnlySpan utf8PropertyName, string value) + => WriteString(utf8PropertyName, value.AsSpan()); private void WriteStringEscapeValueOnly(ReadOnlySpan escapedPropertyName, ReadOnlySpan value, int firstEscapeIndex) { @@ -328,8 +253,8 @@ private void WriteStringEscape(ReadOnlySpan propertyName, ReadOnlySpan= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -347,8 +272,8 @@ private void WriteStringEscape(ReadOnlySpan utf8PropertyName, ReadOnlySpan int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -366,8 +291,8 @@ private void WriteStringEscape(ReadOnlySpan propertyName, ReadOnlySpan= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -385,8 +310,8 @@ private void WriteStringEscape(ReadOnlySpan utf8PropertyName, ReadOnlySpan int valueIdx = JsonWriterHelper.NeedsEscaping(value); int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length && valueIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length && propertyIdx < int.MaxValue / 2); // Equivalent to: valueIdx != -1 || propertyIdx != -1 if (valueIdx + propertyIdx != -2) @@ -412,20 +337,21 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan propertyName, R int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); value = escapedValue.Slice(0, written); } @@ -435,20 +361,21 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan propertyName, R int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); propertyName = escapedPropertyName.Slice(0, written); } @@ -479,20 +406,21 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan utf8PropertyNam int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); utf8Value = escapedValue.Slice(0, written); } @@ -500,21 +428,23 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan utf8PropertyNam if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); utf8PropertyName = escapedPropertyName.Slice(0, written); } @@ -545,20 +475,21 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan propertyName, R int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); utf8Value = escapedValue.Slice(0, written); } @@ -566,21 +497,23 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan propertyName, R if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); propertyName = escapedPropertyName.Slice(0, written); } @@ -611,20 +544,21 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan utf8PropertyNam int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); Span escapedValue; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { valueArray = ArrayPool.Shared.Rent(length); escapedValue = valueArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { char* ptr = stackalloc char[length]; escapedValue = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); value = escapedValue.Slice(0, written); } @@ -632,21 +566,23 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan utf8PropertyNam if (firstEscapeIndexProp != -1) { int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); + Span escapedPropertyName; - if (length > StackallocThreshold) + if (length > JsonConstants.StackallocThreshold) { propertyArray = ArrayPool.Shared.Rent(length); escapedPropertyName = propertyArray; } else { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. + // Cannot create a span directly since it gets assigned to parameter and passed down. unsafe { byte* ptr = stackalloc byte[length]; escapedPropertyName = new Span(ptr, length); } } + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); utf8PropertyName = escapedPropertyName.Slice(0, written); } @@ -667,7 +603,7 @@ private void WriteStringEscapePropertyOrValue(ReadOnlySpan utf8PropertyNam private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, value); } @@ -680,7 +616,7 @@ private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan< private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan utf8Value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, utf8Value); } @@ -693,7 +629,7 @@ private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlyS private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan utf8Value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(propertyName, utf8Value); } @@ -706,7 +642,7 @@ private void WriteStringByOptions(ReadOnlySpan propertyName, ReadOnlySpan< private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlySpan value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8PropertyName, value); } @@ -716,122 +652,342 @@ private void WriteStringByOptions(ReadOnlySpan utf8PropertyName, ReadOnlyS } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 6) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - escapedValue.Length - 6); + + int minRequired = escapedPropertyName.Length + escapedValue.Length + 5; // 2 quotes for property name, 2 quotes for value, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 6); + + // All ASCII, 2 quotes for property name, 2 quotes for value, and 1 colon => escapedPropertyName.Length + escapedValue.Length + 5 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 6; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < ((int.MaxValue - 7 - indent - s_newLineLength) / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length); - WriteStringValue(escapedValue, ref idx); + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + ((escapedPropertyName.Length + escapedValue.Length) * JsonConstants.MaxExpansionFactorWhileTranscoding) + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteStringValue(escapedValue, ref idx); + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - escapedValue.Length - 7 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + escapedValue.Length + 6; // 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteStringValue(escapedValue, ref idx); + Span output = _memory.Span; - Advance(idx); - } + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) - { - int idx = WritePropertyNameIndented(escapedPropertyName); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - WriteStringValue(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(ReadOnlySpan escapedValue, ref int idx) + // TODO: https://github.com/dotnet/corefx/issues/36958 + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedValue.Length + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedValue); - int partialConsumed = 0; - while (true) + Span output = _memory.Span; + + if (_currentDepth < 0) { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.Quote; + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } - private void WriteStringValue(ReadOnlySpan escapedValue, ref int idx) + // TODO: https://github.com/dotnet/corefx/issues/36958 + private void WriteStringIndented(ReadOnlySpan escapedPropertyName, ReadOnlySpan escapedValue) { - if (_buffer.Length <= idx) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length <= JsonConstants.MaxTokenSize); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - escapedValue.Length - 7 - indent - s_newLineLength); + + // All ASCII, 2 quotes for property name, 2 quotes for value, 1 colon, and 1 space => escapedPropertyName.Length + escapedValue.Length + 6 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + escapedPropertyName.Length + 7 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Quote; - CopyLoop(escapedValue, ref idx); + Span output = _memory.Span; - if (_buffer.Length <= idx) + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = JsonConstants.Quote; + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs index ee85f7d84ee2..8ba0f3cf5a05 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteProperties.UnsignedNumber.cs @@ -8,14 +8,16 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -26,15 +28,17 @@ public ref partial struct Utf8JsonWriter /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(string propertyName, ulong value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), value, escape); + public void WriteNumber(string propertyName, ulong value) + => WriteNumber(propertyName.AsSpan(), value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -45,18 +49,11 @@ public void WriteNumber(string propertyName, ulong value, bool escape = true) /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan propertyName, ulong value, bool escape = true) + public void WriteNumber(ReadOnlySpan propertyName, ulong value) { JsonWriterHelper.ValidateProperty(propertyName); - if (escape) - { - WriteNumberEscape(propertyName, value); - } - else - { - WriteNumberByOptions(propertyName, value); - } + WriteNumberEscape(propertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -67,7 +64,9 @@ public void WriteNumber(ReadOnlySpan propertyName, ulong value, bool escap /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -78,18 +77,11 @@ public void WriteNumber(ReadOnlySpan propertyName, ulong value, bool escap /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value, bool escape = true) + public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value) { JsonWriterHelper.ValidateProperty(utf8PropertyName); - if (escape) - { - WriteNumberEscape(utf8PropertyName, value); - } - else - { - WriteNumberByOptions(utf8PropertyName, value); - } + WriteNumberEscape(utf8PropertyName, value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.Number; @@ -100,7 +92,9 @@ public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value, bool e /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -111,15 +105,17 @@ public void WriteNumber(ReadOnlySpan utf8PropertyName, ulong value, bool e /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(string propertyName, uint value, bool escape = true) - => WriteNumber(propertyName.AsSpan(), (ulong)value, escape); + public void WriteNumber(string propertyName, uint value) + => WriteNumber(propertyName.AsSpan(), (ulong)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -130,15 +126,17 @@ public void WriteNumber(string propertyName, uint value, bool escape = true) /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan propertyName, uint value, bool escape = true) - => WriteNumber(propertyName, (ulong)value, escape); + public void WriteNumber(ReadOnlySpan propertyName, uint value) + => WriteNumber(propertyName, (ulong)value); /// /// Writes the property name and value (as a JSON number) as part of a name/value pair of a JSON object. /// /// The UTF-8 encoded property name of the JSON object to be written. /// The value to be written as a JSON number as part of the name/value pair. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -149,14 +147,14 @@ public void WriteNumber(ReadOnlySpan propertyName, uint value, bool escape /// Writes the using the default (i.e. 'G'), for example: 32767. /// [CLSCompliant(false)] - public void WriteNumber(ReadOnlySpan utf8PropertyName, uint value, bool escape = true) - => WriteNumber(utf8PropertyName, (ulong)value, escape); + public void WriteNumber(ReadOnlySpan utf8PropertyName, uint value) + => WriteNumber(utf8PropertyName, (ulong)value); private void WriteNumberEscape(ReadOnlySpan propertyName, ulong value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -172,7 +170,7 @@ private void WriteNumberEscape(ReadOnlySpan utf8PropertyName, ulong value) { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -192,21 +190,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan propertyName, ulong va char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -225,21 +213,11 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, ulon byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteNumberByOptions(escapedPropertyName.Slice(0, written), value); @@ -253,7 +231,7 @@ private void WriteNumberEscapeProperty(ReadOnlySpan utf8PropertyName, ulon private void WriteNumberByOptions(ReadOnlySpan propertyName, ulong value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(propertyName, value); } @@ -266,7 +244,7 @@ private void WriteNumberByOptions(ReadOnlySpan propertyName, ulong value) private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, ulong value) { ValidateWritingProperty(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberIndented(utf8PropertyName, value); } @@ -278,49 +256,152 @@ private void WriteNumberByOptions(ReadOnlySpan utf8PropertyName, ulong val private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - JsonConstants.MaximumFormatUInt64Length - 4); - WriteNumberValueFormatLoop(value, ref idx); + // All ASCII, 2 quotes for property name, and 1 colon => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 4; - Advance(idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedPropertyName, output); + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberMinimized(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameMinimized(escapedPropertyName); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - JsonConstants.MaximumFormatUInt64Length - 4); + + int minRequired = escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 3; // 2 quotes for property name, and 1 colon + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; - Advance(idx); + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedPropertyName.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); + + // All ASCII, 2 quotes for property name, 1 colon, and 1 space => escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedPropertyName.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + JsonConstants.MaximumFormatUInt64Length + 5 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteNumberValueFormatLoop(value, ref idx); + TranscodeAndWrite(escapedPropertyName, output); - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberIndented(ReadOnlySpan escapedPropertyName, ulong value) { - int idx = WritePropertyNameIndented(escapedPropertyName); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - WriteNumberValueFormatLoop(value, ref idx); + Debug.Assert(escapedPropertyName.Length < int.MaxValue - indent - JsonConstants.MaximumFormatUInt64Length - 5 - s_newLineLength); - Advance(idx); - } + int minRequired = indent + escapedPropertyName.Length + JsonConstants.MaximumFormatUInt64Length + 4; // 2 quotes for property name, 1 colon, and 1 space + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line - private void WriteNumberValueFormatLoop(ulong value, ref int idx) - { - if (!Utf8Formatter.TryFormat(value, _buffer.Slice(idx), out int bytesWritten)) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx, JsonConstants.MaximumFormatUInt64Length); - bool result = Utf8Formatter.TryFormat(value, _buffer, out bytesWritten); - Debug.Assert(result); + Grow(maxRequired); } - idx += bytesWritten; + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + escapedPropertyName.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedPropertyName.Length; + + output[BytesPending++] = JsonConstants.Quote; + output[BytesPending++] = JsonConstants.KeyValueSeperator; + output[BytesPending++] = JsonConstants.Space; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs index dc749fa65615..ede191813948 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Comment.cs @@ -8,60 +8,49 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { + private static char[] s_singleLineCommentDelimiter = new char[2] { '*', '/' }; + private static ReadOnlySpan SingleLineCommentDelimiterUtf8 => new byte[2] { (byte)'*', (byte)'/' }; + /// /// Writes the string text value (as a JSON comment). /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given string text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(string value, bool escape = true) - => WriteCommentValue(value.AsSpan(), escape); + public void WriteCommentValue(string value) + => WriteCommentValue(value.AsSpan()); /// /// Writes the UTF-16 text value (as a JSON comment). /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given UTF-16 text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(ReadOnlySpan value, bool escape = true) + public void WriteCommentValue(ReadOnlySpan value) { JsonWriterHelper.ValidateValue(value); - if (escape) + if (value.IndexOf(s_singleLineCommentDelimiter) != -1) { - WriteCommentEscape(value); + ThrowHelper.ThrowArgumentException_InvalidCommentValue(); } - else - { - WriteCommentByOptions(value); - } - } - private void WriteCommentEscape(ReadOnlySpan value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(value); - - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - - if (valueIdx != -1) - { - WriteCommentEscapeValue(value, valueIdx); - } - else - { - WriteCommentByOptions(value); - } + WriteCommentByOptions(value); } private void WriteCommentByOptions(ReadOnlySpan value) { - if (_writerOptions.Indented) + if (Options.Indented) { WriteCommentIndented(value); } @@ -71,115 +60,96 @@ private void WriteCommentByOptions(ReadOnlySpan value) } } - private void WriteCommentMinimized(ReadOnlySpan escapedValue) - { - int idx = 0; - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentIndented(ReadOnlySpan escapedValue) + private void WriteCommentMinimized(ReadOnlySpan value) { - int idx = 0; + Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 4); - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + // All ASCII, /*...*/ => escapedValue.Length + 4 + // Optionally, up to 3x growth when transcoding + int maxRequired = (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4; - int indent = Indentation; - while (true) + if (_memory.Length - BytesPending < maxRequired) { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - WriteCommentValue(escapedValue, ref idx); + Span output = _memory.Span; - Advance(idx); + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(value); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written); + Debug.Assert(status != OperationStatus.DestinationTooSmall); + BytesPending += written; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } - private void WriteCommentEscapeValue(ReadOnlySpan value, int firstEscapeIndexVal) + private void WriteCommentIndented(ReadOnlySpan value) { - Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= value.Length); - Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < value.Length); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - char[] valueArray = null; + Debug.Assert(value.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 4 - s_newLineLength); - int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); + // All ASCII, /*...*/ => escapedValue.Length + 4 + // Optionally, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (value.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 4 + s_newLineLength; - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else + if (_memory.Length - BytesPending < maxRequired) { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedValue = new Span(ptr, length); - } + Grow(maxRequired); } - JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); - WriteCommentByOptions(escapedValue.Slice(0, written)); + Span output = _memory.Span; - if (valueArray != null) + if (_tokenType != JsonTokenType.None) { - ArrayPool.Shared.Return(valueArray); + WriteNewLine(output); } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(value); + OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan, output.Slice(BytesPending), out int _, out int written); + Debug.Assert(status != OperationStatus.DestinationTooSmall); + BytesPending += written; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } /// /// Writes the UTF-8 text value (as a JSON comment). /// /// The UTF-8 encoded value to be written as a JSON comment within /*..*/. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The comment value is not escaped before writing. + /// /// - /// Thrown when the specified value is too large. + /// Thrown when the specified value is too large OR if the given UTF-8 text value contains a comment delimiter (i.e. */). /// - public void WriteCommentValue(ReadOnlySpan utf8Value, bool escape = true) + public void WriteCommentValue(ReadOnlySpan utf8Value) { JsonWriterHelper.ValidateValue(utf8Value); - if (escape) + if (utf8Value.IndexOf(SingleLineCommentDelimiterUtf8) != -1) { - WriteCommentEscape(utf8Value); + ThrowHelper.ThrowArgumentException_InvalidCommentValue(); } - else - { - WriteCommentByOptions(utf8Value); - } - } - private void WriteCommentEscape(ReadOnlySpan utf8Value) - { - int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); - - if (valueIdx != -1) - { - WriteCommentEscapeValue(utf8Value, valueIdx); - } - else - { - WriteCommentByOptions(utf8Value); - } + WriteCommentByOptions(utf8Value); } private void WriteCommentByOptions(ReadOnlySpan utf8Value) { - if (_writerOptions.Indented) + if (Options.Indented) { WriteCommentIndented(utf8Value); } @@ -189,127 +159,62 @@ private void WriteCommentByOptions(ReadOnlySpan utf8Value) } } - private void WriteCommentMinimized(ReadOnlySpan escapedValue) - { - int idx = 0; - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentIndented(ReadOnlySpan escapedValue) - { - int idx = 0; - WriteFormattingPreamble(ref idx); - - WriteCommentValue(escapedValue, ref idx); - - Advance(idx); - } - - private void WriteCommentEscapeValue(ReadOnlySpan utf8Value, int firstEscapeIndexVal) + private void WriteCommentMinimized(ReadOnlySpan utf8Value) { - Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length); - Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length); + Debug.Assert(utf8Value.Length < int.MaxValue - 4); - byte[] valueArray = null; + int maxRequired = utf8Value.Length + 4; // /*...*/ - int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else + if (_memory.Length - BytesPending < maxRequired) { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedValue = new Span(ptr, length); - } + Grow(maxRequired); } - JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); - WriteCommentByOptions(escapedValue.Slice(0, written)); + Span output = _memory.Span; - if (valueArray != null) - { - ArrayPool.Shared.Return(valueArray); - } + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; + + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } - private void WriteCommentValue(ReadOnlySpan escapedValue, ref int idx) + private void WriteCommentIndented(ReadOnlySpan utf8Value) { - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 4 - s_newLineLength); - ReadOnlySpan byteSpan = MemoryMarshal.AsBytes(escapedValue); - int partialConsumed = 0; - while (true) - { - OperationStatus status = JsonWriterHelper.ToUtf8(byteSpan.Slice(partialConsumed), _buffer.Slice(idx), out int consumed, out int written); - idx += written; - if (status == OperationStatus.Done) - { - break; - } - partialConsumed += consumed; - AdvanceAndGrow(ref idx); - } + int minRequired = indent + utf8Value.Length + 4; // /*...*/ + int maxRequired = minRequired + s_newLineLength; // Optionally, 1-2 bytes for new line - if (_buffer.Length <= idx) + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = JsonConstants.Asterisk; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; - } + Span output = _memory.Span; - private void WriteCommentValue(ReadOnlySpan escapedValue, ref int idx) - { - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = JsonConstants.Slash; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - CopyLoop(escapedValue, ref idx); + output[BytesPending++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Asterisk; + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.Slash; + output[BytesPending++] = JsonConstants.Asterisk; + output[BytesPending++] = JsonConstants.Slash; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs index d68d46144e85..b62679a6c7a9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTime.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +23,7 @@ public ref partial struct Utf8JsonWriter public void WriteStringValue(DateTime value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +38,72 @@ public void WriteStringValue(DateTime value) private void WriteStringValueMinimized(DateTime value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(DateTime value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - WriteStringValue(value, ref idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } + + private static readonly StandardFormat s_dateTimeStandardFormat = new StandardFormat('O'); } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs index 3edbf641d7e5..45d9aeb30ec6 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.DateTimeOffset.cs @@ -3,10 +3,13 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; + namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +24,7 @@ public ref partial struct Utf8JsonWriter public void WriteStringValue(DateTimeOffset value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +39,70 @@ public void WriteStringValue(DateTimeOffset value) private void WriteStringValueMinimized(DateTimeOffset value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDateTimeOffsetLength + 3; // 2 quotes, and optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteStringValue(value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + output[BytesPending++] = JsonConstants.Quote; + + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(DateTimeOffset value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatDateTimeOffsetLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteStringValue(value, ref idx); + Span tempSpan = stackalloc byte[JsonConstants.MaximumFormatDateTimeOffsetLength]; + bool result = Utf8Formatter.TryFormat(value, tempSpan, out int bytesWritten, s_dateTimeStandardFormat); + Debug.Assert(result); + JsonWriterHelper.TrimDateTimeOffset(tempSpan.Slice(0, bytesWritten), out bytesWritten); + tempSpan.Slice(0, bytesWritten).CopyTo(output.Slice(BytesPending)); + BytesPending += bytesWritten; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs index 305cf852ec8a..2ad2b1d8197d 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Decimal.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -21,7 +23,7 @@ public ref partial struct Utf8JsonWriter public void WriteNumberValue(decimal value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -36,21 +38,55 @@ public void WriteNumberValue(decimal value) private void WriteNumberValueMinimized(decimal value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDecimalLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(decimal value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatDecimalLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs index 54fc04680c8b..74315e7ae1a8 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Double.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,7 +25,7 @@ public void WriteNumberValue(double value) JsonWriterHelper.ValidateDouble(value); ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -38,21 +40,55 @@ public void WriteNumberValue(double value) private void WriteNumberValueMinimized(double value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatDoubleLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(double value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatDoubleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs index ad8dd12cc77a..47aca2564ba9 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Float.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,7 +25,7 @@ public void WriteNumberValue(float value) JsonWriterHelper.ValidateSingle(value); ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -38,21 +40,55 @@ public void WriteNumberValue(float value) private void WriteNumberValueMinimized(float value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatSingleLength + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(float value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatSingleLength + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs index a0f17efa7c96..f5c245886145 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.FormattedNumber.cs @@ -3,10 +3,11 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -23,10 +24,11 @@ public ref partial struct Utf8JsonWriter /// internal void WriteNumberValue(ReadOnlySpan utf8FormattedNumber) { + JsonWriterHelper.ValidateValue(utf8FormattedNumber); JsonWriterHelper.ValidateNumber(utf8FormattedNumber); - ValidateWritingValue(); - if (_writerOptions.Indented) + + if (Options.Indented) { WriteNumberValueIndented(utf8FormattedNumber); } @@ -39,23 +41,57 @@ internal void WriteNumberValue(ReadOnlySpan utf8FormattedNumber) _tokenType = JsonTokenType.Number; } - private void WriteNumberValueMinimized(ReadOnlySpan value) + private void WriteNumberValueMinimized(ReadOnlySpan utf8Value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - WriteNumberValueFormatLoop(value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } - private void WriteNumberValueIndented(ReadOnlySpan value) + private void WriteNumberValueIndented(ReadOnlySpan utf8Value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(utf8Value.Length < int.MaxValue - indent - 1 - s_newLineLength); + + int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs index a6d17ce081fa..59b87f102ced 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Guid.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON string) as an element of a JSON array. @@ -21,7 +23,7 @@ public ref partial struct Utf8JsonWriter public void WriteStringValue(Guid value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringValueIndented(value); } @@ -36,21 +38,64 @@ public void WriteStringValue(Guid value) private void WriteStringValueMinimized(Guid value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatGuidLength + 3; // 2 quotes, and optionally, 1 list separator - WriteStringValue(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringValueIndented(Guid value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + // 2 quotes, and optionally, 1 list separator and 1-2 bytes for new line + int maxRequired = indent + JsonConstants.MaximumFormatGuidLength + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = JsonConstants.Quote; - WriteStringValue(value, ref idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs index bdbd8f87cf24..cc4986eab577 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Helpers.cs @@ -6,11 +6,11 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { private void ValidateWritingValue() { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { if (_inObject) { @@ -26,44 +26,5 @@ private void ValidateWritingValue() } } } - - private int WriteCommaAndFormattingPreamble() - { - int idx = 0; - WriteListSeparator(ref idx); - WriteFormattingPreamble(ref idx); - return idx; - } - - private void WriteFormattingPreamble(ref int idx) - { - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); - - int indent = Indentation; - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } - } - - private void WriteListSeparator(ref int idx) - { - if (_currentDepth < 0) - { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; - } - } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs index 03d57f2823c0..2a7abf4dcd70 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.Literal.cs @@ -2,9 +2,11 @@ // 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.Diagnostics; + namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the JSON literal "null" as an element of a JSON array. @@ -46,7 +48,7 @@ public void WriteBooleanValue(bool value) private void WriteLiteralByOptions(ReadOnlySpan utf8Value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteLiteralIndented(utf8Value); } @@ -58,21 +60,56 @@ private void WriteLiteralByOptions(ReadOnlySpan utf8Value) private void WriteLiteralMinimized(ReadOnlySpan utf8Value) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(utf8Value.Length <= 5); + + int maxRequired = utf8Value.Length + 1; // Optionally, 1 list separator + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - CopyLoop(utf8Value, ref idx); + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } private void WriteLiteralIndented(ReadOnlySpan utf8Value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + Debug.Assert(utf8Value.Length <= 5); + + int maxRequired = indent + utf8Value.Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - CopyLoop(utf8Value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + utf8Value.CopyTo(output.Slice(BytesPending)); + BytesPending += utf8Value.Length; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs index d32224db5f0e..5f772ea1f907 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.SignedNumber.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -34,7 +36,7 @@ public void WriteNumberValue(int value) public void WriteNumberValue(long value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -49,21 +51,55 @@ public void WriteNumberValue(long value) private void WriteNumberValueMinimized(long value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatInt64Length + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(long value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs index e9513bbfbc60..c3422b19105c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.String.cs @@ -7,45 +7,42 @@ namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the string text value (as a JSON string) as an element of a JSON array. /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(string value, bool escape = true) - => WriteStringValue(value.AsSpan(), escape); + public void WriteStringValue(string value) + => WriteStringValue(value.AsSpan()); /// /// Writes the UTF-16 text value (as a JSON string) as an element of a JSON array. /// /// The UTF-16 encoded value to be written as a UTF-8 transcoded JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(ReadOnlySpan value, bool escape = true) + public void WriteStringValue(ReadOnlySpan value) { JsonWriterHelper.ValidateValue(value); - if (escape) - { - WriteStringEscape(value); - } - else - { - WriteStringByOptions(value); - } + WriteStringEscape(value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -55,7 +52,7 @@ private void WriteStringEscape(ReadOnlySpan value) { int valueIdx = JsonWriterHelper.NeedsEscaping(value); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < value.Length); if (valueIdx != -1) { @@ -70,7 +67,7 @@ private void WriteStringEscape(ReadOnlySpan value) private void WriteStringByOptions(ReadOnlySpan value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(value); } @@ -80,23 +77,70 @@ private void WriteStringByOptions(ReadOnlySpan value) } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedValue) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - 3); + + // All ASCII, 2 quotes => escapedValue.Length + 2 + // Optionally, 1 list separator, and up to 3x growth when transcoding + int maxRequired = (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3; - WriteStringValue(escapedValue, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedValue) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length < (int.MaxValue / JsonConstants.MaxExpansionFactorWhileTranscoding) - indent - 3 - s_newLineLength); + + // All ASCII, 2 quotes => indent + escapedValue.Length + 2 + // Optionally, 1 list separator, 1-2 bytes for new line, and up to 3x growth when transcoding + int maxRequired = indent + (escapedValue.Length * JsonConstants.MaxExpansionFactorWhileTranscoding) + 3 + s_newLineLength; + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteStringValue(escapedValue, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + TranscodeAndWrite(escapedValue, output); + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringEscapeValue(ReadOnlySpan value, int firstEscapeIndexVal) @@ -108,21 +152,10 @@ private void WriteStringEscapeValue(ReadOnlySpan value, int firstEscapeInd int length = JsonWriterHelper.GetMaxEscapedLength(value.Length, firstEscapeIndexVal); - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedValue = new Span(ptr, length); - } - } + Span escapedValue = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (valueArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(value, escapedValue, firstEscapeIndexVal, out int written); WriteStringByOptions(escapedValue.Slice(0, written)); @@ -137,25 +170,20 @@ private void WriteStringEscapeValue(ReadOnlySpan value, int firstEscapeInd /// Writes the UTF-8 text value (as a JSON string) as an element of a JSON array. /// /// The UTF-8 encoded value to be written as a JSON string element of a JSON array. - /// If this is set to false, the writer assumes the value is properly escaped and skips the escaping step. + /// + /// The value is escaped before writing. + /// /// /// Thrown when the specified value is too large. /// /// /// Thrown if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStringValue(ReadOnlySpan utf8Value, bool escape = true) + public void WriteStringValue(ReadOnlySpan utf8Value) { JsonWriterHelper.ValidateValue(utf8Value); - if (escape) - { - WriteStringEscape(utf8Value); - } - else - { - WriteStringByOptions(utf8Value); - } + WriteStringEscape(utf8Value); SetFlagToAddListSeparatorBeforeNextItem(); _tokenType = JsonTokenType.String; @@ -165,7 +193,7 @@ private void WriteStringEscape(ReadOnlySpan utf8Value) { int valueIdx = JsonWriterHelper.NeedsEscaping(utf8Value); - Debug.Assert(valueIdx >= -1 && valueIdx < int.MaxValue / 2); + Debug.Assert(valueIdx >= -1 && valueIdx < utf8Value.Length); if (valueIdx != -1) { @@ -180,7 +208,7 @@ private void WriteStringEscape(ReadOnlySpan utf8Value) private void WriteStringByOptions(ReadOnlySpan utf8Value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteStringIndented(utf8Value); } @@ -190,23 +218,70 @@ private void WriteStringByOptions(ReadOnlySpan utf8Value) } } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringMinimized(ReadOnlySpan escapedValue) { - int idx = 0; - WriteListSeparator(ref idx); + Debug.Assert(escapedValue.Length < int.MaxValue - 3); + + int minRequired = escapedValue.Length + 2; // 2 quotes + int maxRequired = minRequired + 1; // Optionally, 1 list separator - WriteStringValue(escapedValue, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } - Advance(idx); + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } + // TODO: https://github.com/dotnet/corefx/issues/36958 private void WriteStringIndented(ReadOnlySpan escapedValue) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + Debug.Assert(escapedValue.Length < int.MaxValue - indent - 3 - s_newLineLength); + + int minRequired = indent + escapedValue.Length + 2; // 2 quotes + int maxRequired = minRequired + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteStringValue(escapedValue, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + output[BytesPending++] = JsonConstants.Quote; + + escapedValue.CopyTo(output.Slice(BytesPending)); + BytesPending += escapedValue.Length; + + output[BytesPending++] = JsonConstants.Quote; } private void WriteStringEscapeValue(ReadOnlySpan utf8Value, int firstEscapeIndexVal) @@ -218,21 +293,10 @@ private void WriteStringEscapeValue(ReadOnlySpan utf8Value, int firstEscap int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal); - Span escapedValue; - if (length > StackallocThreshold) - { - valueArray = ArrayPool.Shared.Rent(length); - escapedValue = valueArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedValue = new Span(ptr, length); - } - } + Span escapedValue = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (valueArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, out int written); WriteStringByOptions(escapedValue.Slice(0, written)); diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs index 107bc0ad50b2..7659709a690c 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.WriteValues.UnsignedNumber.cs @@ -3,10 +3,12 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Buffers.Text; +using System.Diagnostics; namespace System.Text.Json { - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter { /// /// Writes the value (as a JSON number) as an element of a JSON array. @@ -36,7 +38,7 @@ public void WriteNumberValue(uint value) public void WriteNumberValue(ulong value) { ValidateWritingValue(); - if (_writerOptions.Indented) + if (Options.Indented) { WriteNumberValueIndented(value); } @@ -51,21 +53,55 @@ public void WriteNumberValue(ulong value) private void WriteNumberValueMinimized(ulong value) { - int idx = 0; - WriteListSeparator(ref idx); + int maxRequired = JsonConstants.MaximumFormatUInt64Length + 1; // Optionally, 1 list separator - WriteNumberValueFormatLoop(value, ref idx); + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; - Advance(idx); + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } private void WriteNumberValueIndented(ulong value) { - int idx = WriteCommaAndFormattingPreamble(); + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int maxRequired = indent + JsonConstants.MaximumFormatUInt64Length + 1 + s_newLineLength; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) + { + Grow(maxRequired); + } + + Span output = _memory.Span; + + if (_currentDepth < 0) + { + output[BytesPending++] = JsonConstants.ListSeparator; + } + + if (_tokenType != JsonTokenType.None) + { + WriteNewLine(output); + } - WriteNumberValueFormatLoop(value, ref idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; - Advance(idx); + bool result = Utf8Formatter.TryFormat(value, output.Slice(BytesPending), out int bytesWritten); + Debug.Assert(result); + BytesPending += bytesWritten; } } } diff --git a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs index a328f255d190..cdb4a992bdac 100644 --- a/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs +++ b/src/System.Text.Json/src/System/Text/Json/Writer/Utf8JsonWriter.cs @@ -4,57 +4,47 @@ using System.Buffers; using System.Diagnostics; +using System.IO; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace System.Text.Json { /// /// Provides a high-performance API for forward-only, non-cached writing of UTF-8 encoded JSON text. + /// + /// /// It writes the text sequentially with no caching and adheres to the JSON RFC /// by default (https://tools.ietf.org/html/rfc8259), with the exception of writing comments. - /// + /// /// /// When the user attempts to write invalid JSON and validation is enabled, it throws - /// a with a context specific error message. - /// Since this type is a ref struct, it does not directly support async. However, it does provide - /// support for reentrancy to write partial data, and continue writing in chunks. + /// an with a context specific error message. + /// + /// /// To be able to format the output with indentation and whitespace OR to skip validation, create an instance of - /// and pass that in to the writer. + /// and pass that in to the writer. /// - public ref partial struct Utf8JsonWriter + public sealed partial class Utf8JsonWriter : IDisposable +#if BUILDING_INBOX_LIBRARY + , IAsyncDisposable +#endif { - private const int StackallocThreshold = 256; - private const int DefaultGrowthSize = 4096; + // Depending on OS, either '\r\n' OR '\n' + private static readonly int s_newLineLength = Environment.NewLine.Length; - private readonly IBufferWriter _output; - private int _buffered; - private Span _buffer; + private const int DefaultGrowthSize = 4096; - /// - /// Returns the total amount of bytes written by the so far - /// for the current instance of the . - /// This includes data that has been written beyond what has already been committed. - /// - public long BytesWritten - { - get - { - Debug.Assert(BytesCommitted <= long.MaxValue - _buffered); - return BytesCommitted + _buffered; - } - } + private IBufferWriter _output; + private Stream _stream; + private ArrayBufferWriter _arrayBufferWriter; - /// - /// Returns the total amount of bytes committed to the output by the so far - /// for the current instance of the . - /// This is how much the IBufferWriter has advanced. - /// - public long BytesCommitted { get; private set; } + private Memory _memory; private bool _inObject; private bool _isNotPrimitive; private JsonTokenType _tokenType; - private readonly JsonWriterOptions _writerOptions; private BitStack _bitStack; // The highest order bit of _currentDepth is used to discern whether we are writing the first item in a list or not. @@ -62,6 +52,28 @@ public long BytesWritten // else, no list separator is needed since we are writing the first item. private int _currentDepth; + /// + /// Returns the amount of bytes written by the so far + /// that have not yet been flushed to the output and committed. + /// + public int BytesPending { get; private set; } + + /// + /// Returns the amount of bytes committed to the output by the so far. + /// + /// + /// In the case of IBufferwriter, this is how much the IBufferWriter has advanced. + /// In the case of Stream, this is how much data has been written to the stream. + /// + public long BytesCommitted { get; private set; } + + /// + /// Gets the custom behavior when writing JSON using + /// the which indicates whether to format the output + /// while writing and whether to skip structural JSON validation or not. + /// + public JsonWriterOptions Options { get; } + private int Indentation => CurrentDepth * JsonConstants.SpacesPerIndent; /// @@ -71,99 +83,308 @@ public long BytesWritten public int CurrentDepth => _currentDepth & JsonConstants.RemoveFlagsBitMask; /// - /// Returns the current snapshot of the state which must - /// be captured by the caller and passed back in to the ctor with more data. + /// Constructs a new instance with a specified . /// - /// - /// Thrown when there is JSON data that has been written and buffered but not yet flushed to the . - /// Getting the state for creating a new without first committing the data that has been written - /// would result in an inconsistent state. Call Flush before getting the current state. + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the + /// By default, the writes JSON minimized (i.e. with no extra whitespace) + /// and validates that the JSON being written is structurally valid according to JSON RFC. + /// + /// Thrown when the instance of that is passed in is null. + /// + public Utf8JsonWriter(IBufferWriter bufferWriter, JsonWriterOptions options = default) + { + _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); + _stream = default; + _arrayBufferWriter = default; + + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + Options = options; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } + + /// + /// Constructs a new instance with a specified . + /// + /// An instance of used as a destination for writing JSON text into. + /// Defines the customized behavior of the + /// By default, the writes JSON minimized (i.e. with no extra whitespace) + /// and validates that the JSON being written is structurally valid according to JSON RFC. + /// + /// Thrown when the instance of that is passed in is null. /// + public Utf8JsonWriter(Stream utf8Json, JsonWriterOptions options = default) + { + if (utf8Json == null) + throw new ArgumentNullException(nameof(utf8Json)); + if (!utf8Json.CanWrite) + throw new ArgumentException(SR.StreamNotWritable); + + _stream = utf8Json; + _arrayBufferWriter = new ArrayBufferWriter(); + _output = _arrayBufferWriter; + + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + Options = options; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } + + /// + /// Resets the internal state so that it can be re-used. + /// /// - /// Unlike the , which is a ref struct, the state can survive - /// across async/await boundaries and hence this type is required to provide support for reading - /// in more data asynchronously before continuing with a new instance of the . + /// The will continue to use the original writer options + /// and the original output as the destination (either or ). /// - public JsonWriterState GetCurrentState() + public void Reset() { - if (_buffered != 0) + if (_arrayBufferWriter != null) { - throw ThrowHelper.GetInvalidOperationException_CallFlushFirst(_buffered); + _arrayBufferWriter.Clear(); } - return new JsonWriterState + ResetHelper(); + } + + /// + /// Resets the internal state so that it can be re-used with the new instance of . + /// + /// An instance of used as a destination for writing JSON text into. + /// + /// The will continue to use the original writer options + /// but now write to the passed in as the new destination. + /// + /// + /// Thrown when the instance of that is passed in is null. + /// + /// + /// The instance of has been disposed. + /// + public void Reset(Stream utf8Json) + { + CheckNotDisposed(); + + if (utf8Json == null) + throw new ArgumentNullException(nameof(utf8Json)); + if (!utf8Json.CanWrite) + throw new ArgumentException(SR.StreamNotWritable); + + _stream = utf8Json; + if (_arrayBufferWriter == null) + { + _arrayBufferWriter = new ArrayBufferWriter(); + } + else { - _bytesWritten = BytesWritten, - _bytesCommitted = BytesCommitted, - _inObject = _inObject, - _isNotPrimitive = _isNotPrimitive, - _tokenType = _tokenType, - _currentDepth = _currentDepth, - _writerOptions = _writerOptions, - _bitStack = _bitStack, - }; + _arrayBufferWriter.Clear(); + } + _output = _arrayBufferWriter; + + ResetHelper(); } /// - /// Constructs a new instance with a specified . + /// Resets the internal state so that it can be re-used with the new instance of . /// /// An instance of used as a destination for writing JSON text into. - /// If this is the first call to the ctor, pass in a default state. Otherwise, - /// capture the state from the previous instance of the and pass that back. + /// + /// The will continue to use the original writer options + /// but now write to the passed in as the new destination. + /// /// /// Thrown when the instance of that is passed in is null. /// - /// - /// Since this type is a ref struct, it is a stack-only type and all the limitations of ref structs apply to it. - /// This is the reason why the ctor accepts a . - /// - public Utf8JsonWriter(IBufferWriter bufferWriter, JsonWriterState state = default) + /// + /// The instance of has been disposed. + /// + public void Reset(IBufferWriter bufferWriter) { + CheckNotDisposed(); + _output = bufferWriter ?? throw new ArgumentNullException(nameof(bufferWriter)); - _buffered = 0; - BytesCommitted = 0; - _buffer = _output.GetSpan(); + _stream = default; + + if (_arrayBufferWriter != null) + { + _arrayBufferWriter.Clear(); + } - _inObject = state._inObject; - _isNotPrimitive = state._isNotPrimitive; - _tokenType = state._tokenType; - _writerOptions = state._writerOptions; - _bitStack = state._bitStack; + _arrayBufferWriter = default; - _currentDepth = state._currentDepth; + ResetHelper(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Advance(int count) + private void ResetHelper() { - Debug.Assert(count >= 0 && _buffered <= int.MaxValue - count); + BytesPending = default; + BytesCommitted = default; + _memory = default; + + _inObject = default; + _isNotPrimitive = default; + _tokenType = default; + _currentDepth = default; + + // Only allocate if the user writes a JSON payload beyond the depth that the _allocationFreeContainer can handle. + // This way we avoid allocations in the common, default cases, and allocate lazily. + _bitStack = default; + } - _buffered += count; - _buffer = _buffer.Slice(count); + private void CheckNotDisposed() + { + if (_output == null) + { + throw new ObjectDisposedException(nameof(Utf8JsonWriter)); + } } /// - /// Advances the underlying based on what has been written so far. + /// Commits the JSON text written so far which makes it visible to the output destination. /// - /// Let's the writer know whether more data will be written. This is used to validate - /// that the JSON written so far is structurally valid if no more data is to follow. - /// - /// Thrown when incomplete JSON has been written and is true. - /// (for example when an open object or array needs to be closed). - /// - public void Flush(bool isFinalBlock = true) + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + public void Flush() { - if (isFinalBlock && !_writerOptions.SkipValidation && (CurrentDepth != 0 || _tokenType == JsonTokenType.None)) - ThrowHelper.ThrowInvalidOperationException_DepthNonZeroOrEmptyJson(_currentDepth); + FlushHelper(); + + if (_stream != null) + { + FlushHelperStream(); + _stream.Flush(); + } + + _memory = default; + BytesCommitted += BytesPending; + BytesPending = 0; + } + + /// + /// Commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + /// + /// The instance cannot be re-used after disposing. + /// + public void Dispose() + { + if (_output == null) + { + return; + } Flush(); + ResetHelper(); + + _stream = null; + _arrayBufferWriter = null; + _output = null; + } + +#if BUILDING_INBOX_LIBRARY + /// + /// Asynchronously commits any left over JSON text that has not yet been flushed and releases all resources used by the current instance. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it. + /// + /// + /// The instance cannot be re-used after disposing. + /// + public async ValueTask DisposeAsync() + { + if (_output == null) + { + return; + } + + await FlushAsync().ConfigureAwait(false); + ResetHelper(); + + _stream = null; + _arrayBufferWriter = null; + _output = null; + } +#endif + + /// + /// Asynchronously commits the JSON text written so far which makes it visible to the output destination. + /// + /// + /// In the case of IBufferWriter, this advances the underlying based on what has been written so far. + /// In the case of Stream, this writes the data to the stream and flushes it asynchronously, while monitoring cancellation requests. + /// + public async Task FlushAsync(CancellationToken cancellationToken = default) + { + FlushHelper(); + + if (_stream != null) + { + Debug.Assert(_arrayBufferWriter != null); + Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount); + if (BytesPending != 0) + { +#if BUILDING_INBOX_LIBRARY + await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory, cancellationToken).ConfigureAwait(false); +#else + Debug.Assert(_arrayBufferWriter.WrittenMemory.Length == _arrayBufferWriter.WrittenCount); + await _stream.WriteAsync(_arrayBufferWriter.WrittenMemory.ToArray(), 0, _arrayBufferWriter.WrittenCount, cancellationToken).ConfigureAwait(false); +#endif + _arrayBufferWriter.Clear(); + } + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + _memory = default; + BytesCommitted += BytesPending; + BytesPending = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void FlushHelperStream() + { + Debug.Assert(_arrayBufferWriter != null); + Debug.Assert(BytesPending == _arrayBufferWriter.WrittenCount); + if (BytesPending != 0) + { +#if BUILDING_INBOX_LIBRARY + _stream.Write(_arrayBufferWriter.WrittenSpan); +#else + Debug.Assert(_arrayBufferWriter.WrittenSpan.Length == _arrayBufferWriter.WrittenCount); + _stream.Write(_arrayBufferWriter.WrittenSpan.ToArray(), 0, _arrayBufferWriter.WrittenCount); +#endif + _arrayBufferWriter.Clear(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void Flush() + private void FlushHelper() { - _output.Advance(_buffered); - BytesCommitted += _buffered; - _buffered = 0; + _output.Advance(BytesPending); } /// @@ -197,7 +418,7 @@ private void WriteStart(byte token) if (CurrentDepth >= JsonConstants.MaxWriterDepth) ThrowHelper.ThrowInvalidOperationException(ExceptionResource.DepthTooLarge, _currentDepth, token: default, tokenType: default); - if (_writerOptions.IndentedOrNotSkipValidation) + if (Options.IndentedOrNotSkipValidation) { WriteStartSlow(token); } @@ -213,32 +434,26 @@ private void WriteStart(byte token) private void WriteStartMinimized(byte token) { - int idx = 0; - if (_currentDepth < 0) + if (_memory.Length - BytesPending < 2) // 1 start token, and optionally, 1 list separator { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; + Grow(2); } - if (_buffer.Length <= idx) + Span output = _memory.Span; + if (_currentDepth < 0) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - _buffer[idx++] = token; - - Advance(idx); + output[BytesPending++] = token; } private void WriteStartSlow(byte token) { - Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation); + Debug.Assert(Options.Indented || !Options.SkipValidation); - if (_writerOptions.Indented) + if (Options.Indented) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { ValidateStart(); UpdateBitStackOnStart(token); @@ -247,7 +462,7 @@ private void WriteStartSlow(byte token) } else { - Debug.Assert(!_writerOptions.SkipValidation); + Debug.Assert(!Options.SkipValidation); ValidateStart(); UpdateBitStackOnStart(token); WriteStartMinimized(token); @@ -273,46 +488,42 @@ private void ValidateStart() private void WriteStartIndented(byte token) { - int idx = 0; - if (_currentDepth < 0) + int indent = Indentation; + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + + int minRequired = indent + 1; // 1 start token + int maxRequired = minRequired + 3; // Optionally, 1 list separator and 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - if (_buffer.Length <= idx) - { - GrowAndEnsure(); - } - _buffer[idx++] = JsonConstants.ListSeparator; + Grow(maxRequired); } - if (_tokenType != JsonTokenType.None) - WriteNewLine(ref idx); + Span output = _memory.Span; - int indent = Indentation; - while (true) + if (_currentDepth < 0) { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.ListSeparator; } - if (_buffer.Length <= idx) + if (_tokenType != JsonTokenType.None) { - AdvanceAndGrow(ref idx); + WriteNewLine(output); } - _buffer[idx++] = token; - Advance(idx); + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = token; } /// /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-8 encoded property name of the JSON array to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -320,18 +531,11 @@ private void WriteStartIndented(byte token) /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteStartArray(ReadOnlySpan utf8PropertyName) { ValidatePropertyNameAndDepth(utf8PropertyName); - if (escape) - { - WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket); - } - else - { - WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBracket); - } + WriteStartEscape(utf8PropertyName, JsonConstants.OpenBracket); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -343,7 +547,9 @@ public void WriteStartArray(ReadOnlySpan utf8PropertyName, bool escape = t /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-8 encoded property name of the JSON object to be written. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -351,18 +557,11 @@ public void WriteStartArray(ReadOnlySpan utf8PropertyName, bool escape = t /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(ReadOnlySpan utf8PropertyName, bool escape = true) + public void WriteStartObject(ReadOnlySpan utf8PropertyName) { ValidatePropertyNameAndDepth(utf8PropertyName); - if (escape) - { - WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace); - } - else - { - WriteStartByOptions(utf8PropertyName, JsonConstants.OpenBrace); - } + WriteStartEscape(utf8PropertyName, JsonConstants.OpenBrace); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -374,7 +573,7 @@ private void WriteStartEscape(ReadOnlySpan utf8PropertyName, byte token) { int propertyIdx = JsonWriterHelper.NeedsEscaping(utf8PropertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < utf8PropertyName.Length); if (propertyIdx != -1) { @@ -389,24 +588,15 @@ private void WriteStartEscape(ReadOnlySpan utf8PropertyName, byte token) private void WriteStartByOptions(ReadOnlySpan utf8PropertyName, byte token) { ValidateWritingProperty(token); - int idx; - if (_writerOptions.Indented) + + if (Options.Indented) { - idx = WritePropertyNameIndented(utf8PropertyName); + WritePropertyNameIndented(utf8PropertyName, token); } else { - idx = WritePropertyNameMinimized(utf8PropertyName); - } - - if (1 > _buffer.Length - idx) - { - AdvanceAndGrow(ref idx, 1); + WritePropertyNameMinimized(utf8PropertyName, token); } - - _buffer[idx++] = token; - - Advance(idx); } private void WriteStartEscapeProperty(ReadOnlySpan utf8PropertyName, byte token, int firstEscapeIndexProp) @@ -417,21 +607,10 @@ private void WriteStartEscapeProperty(ReadOnlySpan utf8PropertyName, byte byte[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - byte* ptr = stackalloc byte[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc byte[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, out int written); @@ -447,7 +626,9 @@ private void WriteStartEscapeProperty(ReadOnlySpan utf8PropertyName, byte /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -455,14 +636,16 @@ private void WriteStartEscapeProperty(ReadOnlySpan utf8PropertyName, byte /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(string propertyName, bool escape = true) - => WriteStartArray(propertyName.AsSpan(), escape); + public void WriteStartArray(string propertyName) + => WriteStartArray(propertyName.AsSpan()); /// /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -470,14 +653,16 @@ public void WriteStartArray(string propertyName, bool escape = true) /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(string propertyName, bool escape = true) - => WriteStartObject(propertyName.AsSpan(), escape); + public void WriteStartObject(string propertyName) + => WriteStartObject(propertyName.AsSpan()); /// /// Writes the beginning of a JSON array with a property name as the key. /// /// The UTF-16 encoded property name of the JSON array to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -485,18 +670,11 @@ public void WriteStartObject(string propertyName, bool escape = true) /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartArray(ReadOnlySpan propertyName, bool escape = true) + public void WriteStartArray(ReadOnlySpan propertyName) { ValidatePropertyNameAndDepth(propertyName); - if (escape) - { - WriteStartEscape(propertyName, JsonConstants.OpenBracket); - } - else - { - WriteStartByOptions(propertyName, JsonConstants.OpenBracket); - } + WriteStartEscape(propertyName, JsonConstants.OpenBracket); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -508,7 +686,9 @@ public void WriteStartArray(ReadOnlySpan propertyName, bool escape = true) /// Writes the beginning of a JSON object with a property name as the key. /// /// The UTF-16 encoded property name of the JSON object to be transcoded and written as UTF-8. - /// If this is set to false, the writer assumes the property name is properly escaped and skips the escaping step. + /// + /// The property name is escaped before writing. + /// /// /// Thrown when the specified property name is too large. /// @@ -516,18 +696,11 @@ public void WriteStartArray(ReadOnlySpan propertyName, bool escape = true) /// Thrown when the depth of the JSON has exceeded the maximum depth of 1000 /// OR if this would result in an invalid JSON to be written (while validation is enabled). /// - public void WriteStartObject(ReadOnlySpan propertyName, bool escape = true) + public void WriteStartObject(ReadOnlySpan propertyName) { ValidatePropertyNameAndDepth(propertyName); - if (escape) - { - WriteStartEscape(propertyName, JsonConstants.OpenBrace); - } - else - { - WriteStartByOptions(propertyName, JsonConstants.OpenBrace); - } + WriteStartEscape(propertyName, JsonConstants.OpenBrace); _currentDepth &= JsonConstants.RemoveFlagsBitMask; _currentDepth++; @@ -539,7 +712,7 @@ private void WriteStartEscape(ReadOnlySpan propertyName, byte token) { int propertyIdx = JsonWriterHelper.NeedsEscaping(propertyName); - Debug.Assert(propertyIdx >= -1 && propertyIdx < int.MaxValue / 2); + Debug.Assert(propertyIdx >= -1 && propertyIdx < propertyName.Length); if (propertyIdx != -1) { @@ -554,24 +727,15 @@ private void WriteStartEscape(ReadOnlySpan propertyName, byte token) private void WriteStartByOptions(ReadOnlySpan propertyName, byte token) { ValidateWritingProperty(token); - int idx; - if (_writerOptions.Indented) + + if (Options.Indented) { - idx = WritePropertyNameIndented(propertyName); + WritePropertyNameIndented(propertyName, token); } else { - idx = WritePropertyNameMinimized(propertyName); - } - - if (1 > _buffer.Length - idx) - { - AdvanceAndGrow(ref idx, 1); + WritePropertyNameMinimized(propertyName, token); } - - _buffer[idx++] = token; - - Advance(idx); } private void WriteStartEscapeProperty(ReadOnlySpan propertyName, byte token, int firstEscapeIndexProp) @@ -582,21 +746,11 @@ private void WriteStartEscapeProperty(ReadOnlySpan propertyName, byte toke char[] propertyArray = null; int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp); - Span escapedPropertyName; - if (length > StackallocThreshold) - { - propertyArray = ArrayPool.Shared.Rent(length); - escapedPropertyName = propertyArray; - } - else - { - // Cannot create a span directly since it gets passed to instance methods on a ref struct. - unsafe - { - char* ptr = stackalloc char[length]; - escapedPropertyName = new Span(ptr, length); - } - } + + Span escapedPropertyName = length <= JsonConstants.StackallocThreshold ? + stackalloc char[length] : + (propertyArray = ArrayPool.Shared.Rent(length)); + JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, out int written); WriteStartByOptions(escapedPropertyName.Slice(0, written), token); @@ -633,7 +787,7 @@ public void WriteEndObject() private void WriteEnd(byte token) { - if (_writerOptions.IndentedOrNotSkipValidation) + if (Options.IndentedOrNotSkipValidation) { WriteEndSlow(token); } @@ -652,22 +806,22 @@ private void WriteEnd(byte token) private void WriteEndMinimized(byte token) { - if (_buffer.Length < 1) + if (_memory.Length - BytesPending < 1) // 1 end token { - GrowAndEnsure(); + Grow(1); } - _buffer[0] = token; - Advance(1); + Span output = _memory.Span; + output[BytesPending++] = token; } private void WriteEndSlow(byte token) { - Debug.Assert(_writerOptions.Indented || !_writerOptions.SkipValidation); + Debug.Assert(Options.Indented || !Options.SkipValidation); - if (_writerOptions.Indented) + if (Options.Indented) { - if (!_writerOptions.SkipValidation) + if (!Options.SkipValidation) { ValidateEnd(token); } @@ -675,7 +829,7 @@ private void WriteEndSlow(byte token) } else { - Debug.Assert(!_writerOptions.SkipValidation); + Debug.Assert(!Options.SkipValidation); ValidateEnd(token); WriteEndMinimized(token); } @@ -716,10 +870,8 @@ private void WriteEndIndented(byte token) } else { - int idx = 0; - WriteNewLine(ref idx); - int indent = Indentation; + // Necessary if WriteEndX is called without a corresponding WriteStartX first. if (indent != 0) { @@ -727,46 +879,37 @@ private void WriteEndIndented(byte token) // current depth yet, explicitly subtract here. indent -= JsonConstants.SpacesPerIndent; } - while (true) - { - bool result = JsonWriterHelper.TryWriteIndentation(_buffer.Slice(idx), indent, out int bytesWritten); - idx += bytesWritten; - if (result) - { - break; - } - indent -= bytesWritten; - AdvanceAndGrow(ref idx); - } - if (_buffer.Length <= idx) + Debug.Assert(indent <= 2 * JsonConstants.MaxWriterDepth); + Debug.Assert(Options.SkipValidation || _tokenType != JsonTokenType.None); + + int maxRequired = indent + 3; // 1 end token, 1-2 bytes for new line + + if (_memory.Length - BytesPending < maxRequired) { - AdvanceAndGrow(ref idx); + Grow(maxRequired); } - _buffer[idx++] = token; - Advance(idx); + Span output = _memory.Span; + + WriteNewLine(output); + + JsonWriterHelper.WriteIndentation(output.Slice(BytesPending), indent); + BytesPending += indent; + + output[BytesPending++] = token; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void WriteNewLine(ref int idx) + private void WriteNewLine(Span output) { // Write '\r\n' OR '\n', depending on OS - if (Environment.NewLine.Length == 2) - { - if (_buffer.Length <= idx) - { - AdvanceAndGrow(ref idx); - } - _buffer[idx++] = JsonConstants.CarriageReturn; - } - - if (_buffer.Length <= idx) + if (s_newLineLength == 2) { - AdvanceAndGrow(ref idx); + output[BytesPending++] = JsonConstants.CarriageReturn; } - _buffer[idx++] = JsonConstants.LineFeed; + output[BytesPending++] = JsonConstants.LineFeed; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -785,61 +928,43 @@ private void UpdateBitStackOnStart(byte token) } } - private void GrowAndEnsure() + private void Grow(int requiredSize) { - Flush(); - int previousSpanLength = _buffer.Length; - Debug.Assert(previousSpanLength < DefaultGrowthSize); - _buffer = _output.GetSpan(DefaultGrowthSize); - if (_buffer.Length <= previousSpanLength) - { - ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetLargerSpan); - } - } + Debug.Assert(requiredSize > 0); - private void GrowAndEnsure(int minimumSize) - { - Flush(); - Debug.Assert(minimumSize < DefaultGrowthSize); - _buffer = _output.GetSpan(DefaultGrowthSize); - if (_buffer.Length < minimumSize) + if (_memory.Length == 0) { - ThrowHelper.ThrowArgumentException(ExceptionResource.FailedToGetMinimumSizeSpan, minimumSize); + Debug.Assert(BytesPending == 0); + _memory = _output.GetMemory(); + if (_memory.Length >= requiredSize) + { + return; + } } - } - private void AdvanceAndGrow(ref int alreadyWritten) - { - Debug.Assert(alreadyWritten >= 0); - Advance(alreadyWritten); - GrowAndEnsure(); - alreadyWritten = 0; - } + FlushHelper(); - private void AdvanceAndGrow(ref int alreadyWritten, int minimumSize) - { - Debug.Assert(minimumSize >= 1 && minimumSize <= 128); - Advance(alreadyWritten); - GrowAndEnsure(minimumSize); - alreadyWritten = 0; - } + int sizeHint = Math.Max(DefaultGrowthSize, requiredSize); - private void CopyLoop(ReadOnlySpan span, ref int idx) - { - while (true) + if (_stream != null) { - if (span.Length <= _buffer.Length - idx) + FlushHelperStream(); + _memory = _output.GetMemory(sizeHint); + + Debug.Assert(_memory.Length >= sizeHint); + } + else + { + _memory = _output.GetMemory(sizeHint); + + if (_memory.Length < sizeHint) { - span.CopyTo(_buffer.Slice(idx)); - idx += span.Length; - break; + ThrowHelper.ThrowInvalidOperationException_NeedLargerSpan(); } - - span.Slice(0, _buffer.Length - idx).CopyTo(_buffer.Slice(idx)); - span = span.Slice(_buffer.Length - idx); - idx = _buffer.Length; - AdvanceAndGrow(ref idx); } + + BytesCommitted += BytesPending; + BytesPending = 0; } private void SetFlagToAddListSeparatorBeforeNextItem() diff --git a/src/System.Text.Json/tests/ArrayBufferWriter.cs b/src/System.Text.Json/tests/ArrayBufferWriter.cs deleted file mode 100644 index 13249f80d1c8..000000000000 --- a/src/System.Text.Json/tests/ArrayBufferWriter.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Tests -{ - internal class ArrayBufferWriter : IBufferWriter, IDisposable - { - private ResizableArray _buffer; - - public ArrayBufferWriter(int capacity) - { - _buffer = new ResizableArray(ArrayPool.Shared.Rent(capacity)); - } - - public int CommitedByteCount => _buffer.Count; - - public void Clear() - { - _buffer.Count = 0; - } - - public ArraySegment Free => _buffer.Free; - - public ArraySegment Formatted => _buffer.Full; - - public Memory GetMemory(int minimumLength = 0) - { - if (minimumLength < 1) - { - minimumLength = 1; - } - - if (minimumLength > _buffer.FreeCount) - { - int doubleCount = _buffer.FreeCount * 2; - int newSize = minimumLength > doubleCount ? minimumLength : doubleCount; - byte[] newArray = ArrayPool.Shared.Rent(newSize + _buffer.Count); - byte[] oldArray = _buffer.Resize(newArray); - ArrayPool.Shared.Return(oldArray); - } - - return _buffer.FreeMemory; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span GetSpan(int minimumLength = 0) - { - if (minimumLength < 1) - { - minimumLength = 1; - } - - if (minimumLength > _buffer.FreeCount) - { - int doubleCount = _buffer.FreeCount * 2; - int newSize = minimumLength > doubleCount ? minimumLength : doubleCount; - byte[] newArray = ArrayPool.Shared.Rent(newSize + _buffer.Count); - byte[] oldArray = _buffer.Resize(newArray); - ArrayPool.Shared.Return(oldArray); - } - - return _buffer.FreeSpan; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int bytes) - { - _buffer.Count += bytes; - if (_buffer.Count > _buffer.Capacity) - { - throw new InvalidOperationException("More bytes commited than returned from FreeBuffer"); - } - } - - public void Dispose() - { - byte[] array = _buffer.Array; - _buffer.Array = null; - ArrayPool.Shared.Return(array); - } - } -} diff --git a/src/System.Text.Json/tests/FixedSizedBufferWriter.cs b/src/System.Text.Json/tests/FixedSizedBufferWriter.cs index 79e7c341abd6..b22e796d1de5 100644 --- a/src/System.Text.Json/tests/FixedSizedBufferWriter.cs +++ b/src/System.Text.Json/tests/FixedSizedBufferWriter.cs @@ -26,6 +26,8 @@ public void Clear() public byte[] Formatted => _buffer.AsSpan(0, _count).ToArray(); + public int FormattedCount => _count; + public Memory GetMemory(int minimumLength = 0) => _buffer.AsMemory(_count); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -34,11 +36,11 @@ public void Clear() [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Advance(int bytes) { - _count += bytes; - if (_count > _buffer.Length) + if (_count > _buffer.Length - bytes) { throw new InvalidOperationException("Cannot advance past the end of the buffer."); } + _count += bytes; } } } diff --git a/src/System.Text.Json/tests/JsonDocumentTests.cs b/src/System.Text.Json/tests/JsonDocumentTests.cs index a86221a8db2e..407f5ef26794 100644 --- a/src/System.Text.Json/tests/JsonDocumentTests.cs +++ b/src/System.Text.Json/tests/JsonDocumentTests.cs @@ -494,6 +494,8 @@ private static void ParseJson( using (JsonDocument doc = stringDocBuilder?.Invoke(jsonString) ?? bytesDocBuilder?.Invoke(dataUtf8)) { + Assert.NotNull(doc); + JsonElement rootElement = doc.RootElement; Func expectedFunc = null; @@ -1371,19 +1373,19 @@ public static void CheckUseAfterDispose() Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsValue(ref writer); + root.WriteAsValue(writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); } } @@ -1416,19 +1418,19 @@ public static void CheckUseDefault() Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsValue(ref writer); + root.WriteAsValue(writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); Assert.Throws(() => { Utf8JsonWriter writer = default; - root.WriteAsProperty(ReadOnlySpan.Empty, ref writer); + root.WriteAsProperty(ReadOnlySpan.Empty, writer); }); } diff --git a/src/System.Text.Json/tests/JsonElementCloneTests.cs b/src/System.Text.Json/tests/JsonElementCloneTests.cs new file mode 100644 index 000000000000..74183173cc02 --- /dev/null +++ b/src/System.Text.Json/tests/JsonElementCloneTests.cs @@ -0,0 +1,171 @@ +// 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.Reflection; +using Xunit; + +namespace System.Text.Json.Tests +{ + public static class JsonElementCloneTests + { + [Fact] + public static void CloneTwiceFromSameDocument() + { + string json = "[[]]"; + JsonElement root; + JsonElement clone; + JsonElement clone2; + + using (JsonDocument doc = JsonDocument.Parse(json)) + { + root = doc.RootElement; + clone = root.Clone(); + clone2 = root.Clone(); + + Assert.Equal(json, clone.GetRawText()); + Assert.NotSame(doc, clone.SniffDocument()); + Assert.NotSame(doc, clone2.SniffDocument()); + } + + // After document Dispose + Assert.Equal(json, clone.GetRawText()); + Assert.Equal(json, clone2.GetRawText()); + Assert.NotSame(clone.SniffDocument(), clone2.SniffDocument()); + + Assert.Throws(() => root.GetRawText()); + } + + [Fact] + public static void CloneInnerElementFromClonedElement() + { + JsonElement clone; + + using (JsonDocument doc = JsonDocument.Parse("[[[]]]")) + { + JsonElement middle = doc.RootElement[0].Clone(); + JsonElement inner = middle[0]; + clone = inner.Clone(); + + Assert.Equal(inner.GetRawText(), clone.GetRawText()); + Assert.NotSame(doc, clone.SniffDocument()); + Assert.Same(middle.SniffDocument(), clone.SniffDocument()); + Assert.Same(inner.SniffDocument(), clone.SniffDocument()); + } + + // After document Dispose + Assert.Equal("[]", clone.GetRawText()); + } + + [Fact] + public static void CloneAtInnerNumber() + { + CloneAtInner("1.21e9", JsonValueType.Number); + } + + [Fact] + public static void CloneAtInnerString() + { + CloneAtInner("\" this string has \\u0039 spaces\"", JsonValueType.String); + } + + [Fact] + public static void CloneAtInnerTrue() + { + CloneAtInner("true", JsonValueType.True); + } + + [Fact] + public static void CloneAtInnerFalse() + { + CloneAtInner("false", JsonValueType.False); + } + + [Fact] + public static void CloneAtInnerNull() + { + CloneAtInner("null", JsonValueType.Null); + } + + [Fact] + public static void CloneAtInnerObject() + { + // Very weird whitespace is used here just to ensure that the + // clone API isn't making any whitespace assumptions. + CloneAtInner( + @"{ + ""this"": + [ + { + ""object"": 0, + + + + + ""has"": [ ""whitespace"" ] + } + ] +}", + JsonValueType.Object); + } + + [Fact] + public static void CloneAtInnerArray() + { + // Very weird whitespace is used here just to ensure that the + // clone API isn't making any whitespace assumptions. + CloneAtInner( + @"[ +{ + ""this"": + [ + { + ""object"": 0, + + + + + ""has"": [ ""whitespace"" ] + } + ] +}, + +5 + +, + + + +false, + + + +null +]", + JsonValueType.Array); + } + + private static void CloneAtInner(string innerJson, JsonValueType valueType) + { + string json = $"{{ \"obj\": [ {{ \"not target\": true, \"target\": {innerJson} }}, 5 ] }}"; + + JsonElement clone; + + using (JsonDocument doc = JsonDocument.Parse(json)) + { + JsonElement target = doc.RootElement.GetProperty("obj")[0].GetProperty("target"); + Assert.Equal(valueType, target.Type); + clone = target.Clone(); + } + + Assert.Equal(innerJson, clone.GetRawText()); + } + + private static JsonDocument SniffDocument(this JsonElement element) + { + return (JsonDocument)typeof(JsonElement). + GetField("_parent", BindingFlags.Instance|BindingFlags.NonPublic). + GetValue(element); + } + } +} diff --git a/src/System.Text.Json/tests/JsonElementWriteTests.cs b/src/System.Text.Json/tests/JsonElementWriteTests.cs index ec0a2a3a5dee..70e2008fc93d 100644 --- a/src/System.Text.Json/tests/JsonElementWriteTests.cs +++ b/src/System.Text.Json/tests/JsonElementWriteTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using Xunit; +using System.Buffers; namespace System.Text.Json.Tests { @@ -138,7 +139,7 @@ public static void WriteFalse(bool indented) { WriteSimpleValue(indented, "false"); } - + [Theory] [InlineData(false)] [InlineData(true)] @@ -388,6 +389,26 @@ public static void WriteNumberAsProperty(bool indented) "{\"ectoplasm\":42}"); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public static void WriteNumberAsPropertyWithLargeName(bool indented) + { + var charArray = new char[300]; + charArray.AsSpan().Fill('a'); + charArray[0] = (char)0xEA; + var propertyName = new string(charArray); + + WritePropertyValueBothForms( + indented, + propertyName, + "42", + @"{ + ""\u00ea" + propertyName.Substring(1) + @""": 42 +}", + $"{{\"\\u00ea{propertyName.Substring(1)}\":42}}"); + } + [Theory] [InlineData(false)] [InlineData(true)] @@ -806,15 +827,14 @@ public static void WriteIncredibleDepth() closeBrackets.Fill((byte)']'); jsonIn.AsSpan(SpacesPre + TargetDepth + SpacesSplit + TargetDepth).Fill((byte)' '); - using (ArrayBufferWriter buffer = new ArrayBufferWriter(jsonIn.Length)) + var buffer = new ArrayBufferWriter(jsonIn.Length); using (JsonDocument doc = JsonDocument.Parse(jsonIn, optionsCopy)) { var writer = new Utf8JsonWriter(buffer); - doc.RootElement.WriteAsValue(ref writer); + doc.RootElement.WriteAsValue(writer); writer.Flush(); - ArraySegment formattedSegment = buffer.Formatted; - ReadOnlySpan formatted = formattedSegment; + ReadOnlySpan formatted = buffer.WrittenSpan; Assert.Equal(TargetDepth + TargetDepth, formatted.Length); Assert.True(formatted.Slice(0, TargetDepth).SequenceEqual(openBrackets), "OpenBrackets match"); @@ -827,26 +847,25 @@ public static void WriteIncredibleDepth() [InlineData(true)] public static void WritePropertyOutsideObject(bool skipValidation) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions)) { JsonElement root = doc.RootElement; - JsonWriterState state = new JsonWriterState( - new JsonWriterOptions - { - SkipValidation = skipValidation, - }); + var options = new JsonWriterOptions + { + SkipValidation = skipValidation, + }; const string CharLabel = "char"; byte[] byteUtf8 = Encoding.UTF8.GetBytes("byte"); - Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); if (skipValidation) { foreach (JsonElement val in root.EnumerateArray()) { - val.WriteAsProperty(CharLabel.AsSpan(), ref writer); - val.WriteAsProperty(byteUtf8, ref writer); + val.WriteAsProperty(CharLabel.AsSpan(), writer); + val.WriteAsProperty(byteUtf8, writer); } writer.Flush(); @@ -867,18 +886,14 @@ public static void WritePropertyOutsideObject(bool skipValidation) { JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), ref w)); + (ref Utf8JsonWriter w) => val.WriteAsProperty(CharLabel.AsSpan(), w)); JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, ref w)); + (ref Utf8JsonWriter w) => val.WriteAsProperty(byteUtf8, w)); } - JsonTestHelper.AssertThrows( - ref writer, - (ref Utf8JsonWriter w) => w.Flush()); - - writer.Flush(isFinalBlock: false); + writer.Flush(); AssertContents("", buffer); } @@ -890,24 +905,23 @@ public static void WritePropertyOutsideObject(bool skipValidation) [InlineData(true)] public static void WriteValueInsideObject(bool skipValidation) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (var doc = JsonDocument.Parse("[ null, false, true, \"hi\", 5, {}, [] ]", s_readerOptions)) { JsonElement root = doc.RootElement; - JsonWriterState state = new JsonWriterState( - new JsonWriterOptions - { - SkipValidation = skipValidation, - }); + var options = new JsonWriterOptions + { + SkipValidation = skipValidation, + }; - Utf8JsonWriter writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); if (skipValidation) { foreach (JsonElement val in root.EnumerateArray()) { - val.WriteAsValue(ref writer); + val.WriteAsValue(writer); } writer.WriteEndObject(); @@ -923,7 +937,7 @@ public static void WriteValueInsideObject(bool skipValidation) { JsonTestHelper.AssertThrows( ref writer, - (ref Utf8JsonWriter w) => val.WriteAsValue(ref w)); + (ref Utf8JsonWriter w) => val.WriteAsValue(w)); } writer.WriteEndObject(); @@ -936,20 +950,19 @@ public static void WriteValueInsideObject(bool skipValidation) private static void WriteSimpleValue(bool indented, string jsonIn, string jsonOut = null) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); - target.WriteAsValue(ref writer); + target.WriteAsValue(writer); writer.Flush(); AssertContents(jsonOut ?? jsonIn, buffer); @@ -962,20 +975,19 @@ private static void WriteComplexValue( string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); - target.WriteAsValue(ref writer); + target.WriteAsValue(writer); writer.Flush(); if (indented && s_replaceNewlines) @@ -1018,21 +1030,20 @@ private static void WritePropertyValue( string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); - target.WriteAsProperty(propertyName, ref writer); + target.WriteAsProperty(propertyName, writer); writer.WriteEndObject(); writer.Flush(); @@ -1054,21 +1065,20 @@ private static void WritePropertyValue( string expectedIndent, string expectedMinimal) { - using (ArrayBufferWriter buffer = new ArrayBufferWriter(1024)) + var buffer = new ArrayBufferWriter(1024); using (JsonDocument doc = JsonDocument.Parse($" [ {jsonIn} ]", s_readerOptions)) { JsonElement target = doc.RootElement[0]; - var state = new JsonWriterState( - new JsonWriterOptions - { - Indented = indented, - }); + var options = new JsonWriterOptions + { + Indented = indented, + }; - var writer = new Utf8JsonWriter(buffer, state); + var writer = new Utf8JsonWriter(buffer, options); writer.WriteStartObject(); - target.WriteAsProperty(propertyName, ref writer); + target.WriteAsProperty(propertyName, writer); writer.WriteEndObject(); writer.Flush(); @@ -1083,14 +1093,14 @@ private static void WritePropertyValue( } } - private static void AssertContents(string expectedValue, ArrayBufferWriter buffer) + private static void AssertContents(string expectedValue, ArrayBufferWriter buffer) { Assert.Equal( expectedValue, Encoding.UTF8.GetString( - buffer.Formatted + buffer.WrittenSpan #if netstandard - .AsSpan().ToArray() + .ToArray() #endif )); } diff --git a/src/System.Text.Json/tests/JsonWriterOptionsTests.cs b/src/System.Text.Json/tests/JsonWriterOptionsTests.cs new file mode 100644 index 000000000000..41844819f948 --- /dev/null +++ b/src/System.Text.Json/tests/JsonWriterOptionsTests.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. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.Text.Json.Tests +{ + public static partial class JsonWriterOptionsTests + { + [Fact] + public static void JsonWriterOptionsDefaultCtor() + { + JsonWriterOptions options = default; + + var expectedOption = new JsonWriterOptions + { + Indented = false, + SkipValidation = false + }; + Assert.Equal(expectedOption, options); + } + + [Fact] + public static void JsonWriterOptionsCtor() + { + var options = new JsonWriterOptions(); + + var expectedOption = new JsonWriterOptions + { + Indented = false, + SkipValidation = false + }; + Assert.Equal(expectedOption, options); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public static void JsonWriterOptions(bool indented, bool skipValidation) + { + var options = new JsonWriterOptions(); + options.Indented = indented; + options.SkipValidation = skipValidation; + + var expectedOption = new JsonWriterOptions + { + Indented = indented, + SkipValidation = skipValidation + }; + Assert.Equal(expectedOption, options); + } + } +} diff --git a/src/System.Text.Json/tests/JsonWriterStateTests.cs b/src/System.Text.Json/tests/JsonWriterStateTests.cs deleted file mode 100644 index 87a167008f0e..000000000000 --- a/src/System.Text.Json/tests/JsonWriterStateTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 Xunit; - -namespace System.Text.Json.Tests -{ - public static partial class JsonWriterStateTests - { - [Fact] - public static void DefaultJsonWriterState() - { - JsonWriterState state = default; - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - - [Fact] - public static void JsonWriterStateDefaultCtor() - { - var state = new JsonWriterState(); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - - [Fact] - public static void JsonWriterStateCtor() - { - var state = new JsonWriterState(options: default); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - var expectedOption = new JsonWriterOptions - { - Indented = false, - SkipValidation = false - }; - Assert.Equal(expectedOption, state.Options); - } - } -} diff --git a/src/System.Text.Json/tests/ResizableArray.cs b/src/System.Text.Json/tests/ResizableArray.cs deleted file mode 100644 index b02d6b4c4b12..000000000000 --- a/src/System.Text.Json/tests/ResizableArray.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace System.Text.Json.Tests -{ - // a List like type designed to be embeded in other types - internal struct ResizableArray - { - public ResizableArray(T[] array, int count = 0) - { - Array = array; - Count = count; - } - - public T[] Array { get; set; } - - public int Count { get; set; } - - public int Capacity => Array.Length; - - public T[] Resize(T[] newArray) - { - T[] oldArray = Array; - Array.AsSpan(0, Count).CopyTo(newArray); // CopyTo will throw if newArray.Length < _count - Array = newArray; - return oldArray; - } - - public ArraySegment Full => new ArraySegment(Array, 0, Count); - - public ArraySegment Free => new ArraySegment(Array, Count, Array.Length - Count); - - public Span FreeSpan => new Span(Array, Count, Array.Length - Count); - - public Memory FreeMemory => new Memory(Array, Count, Array.Length - Count); - - public int FreeCount => Array.Length - Count; - } -} diff --git a/src/System.Text.Json/tests/Resources/Strings.resx b/src/System.Text.Json/tests/Resources/Strings.resx index 3249795a3c9b..6133f4a75671 100644 --- a/src/System.Text.Json/tests/Resources/Strings.resx +++ b/src/System.Text.Json/tests/Resources/Strings.resx @@ -20268,4 +20268,7 @@ tiline\"another\" String\\"],"str":"\"\""} } } + + Cannot advance past the end of the buffer, which has a size of {0}. + \ No newline at end of file diff --git a/src/System.Text.Json/tests/Serialization/Array.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Array.ReadTests.cs index d4db11d8a870..b60697653b4d 100644 --- a/src/System.Text.Json/tests/Serialization/Array.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Array.ReadTests.cs @@ -2,12 +2,23 @@ // 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.Collections.Generic; using Xunit; namespace System.Text.Json.Serialization.Tests { public static partial class ArrayTests { + [Fact] + public static void ReadEmpty() + { + SimpleTestClass[] arr = JsonSerializer.Parse("[]"); + Assert.Equal(0, arr.Length); + + List list = JsonSerializer.Parse>("[]"); + Assert.Equal(0, list.Count); + } + [Fact] public static void ReadClassWithStringArray() { @@ -35,5 +46,75 @@ public static void ReadClassWithGenericList() TestClassWithGenericList obj = JsonSerializer.Parse(TestClassWithGenericList.s_data); obj.Verify(); } + + [Fact] + public static void ReadClassWithObjectIEnumerableT() + { + TestClassWithObjectIEnumerableT obj = JsonSerializer.Parse(TestClassWithObjectIEnumerableT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectIListT() + { + TestClassWithObjectIListT obj = JsonSerializer.Parse(TestClassWithObjectIListT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectICollectionT() + { + TestClassWithObjectICollectionT obj = JsonSerializer.Parse(TestClassWithObjectICollectionT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectIReadOnlyCollectionT() + { + TestClassWithObjectIReadOnlyCollectionT obj = JsonSerializer.Parse(TestClassWithObjectIReadOnlyCollectionT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithObjectIReadOnlyListT() + { + TestClassWithObjectIReadOnlyListT obj = JsonSerializer.Parse(TestClassWithObjectIReadOnlyListT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericIEnumerableT() + { + TestClassWithGenericIEnumerableT obj = JsonSerializer.Parse(TestClassWithGenericIEnumerableT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericIListT() + { + TestClassWithGenericIListT obj = JsonSerializer.Parse(TestClassWithGenericIListT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericICollectionT() + { + TestClassWithGenericICollectionT obj = JsonSerializer.Parse(TestClassWithGenericICollectionT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericIReadOnlyCollectionT() + { + TestClassWithGenericIReadOnlyCollectionT obj = JsonSerializer.Parse(TestClassWithGenericIReadOnlyCollectionT.s_data); + obj.Verify(); + } + + [Fact] + public static void ReadClassWithGenericIReadOnlyListT() + { + TestClassWithGenericIReadOnlyListT obj = JsonSerializer.Parse(TestClassWithGenericIReadOnlyListT.s_data); + obj.Verify(); + } } } diff --git a/src/System.Text.Json/tests/Serialization/Array.WriteTests.cs b/src/System.Text.Json/tests/Serialization/Array.WriteTests.cs index 5c0197fbfe72..d6e0715398d5 100644 --- a/src/System.Text.Json/tests/Serialization/Array.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Array.WriteTests.cs @@ -2,12 +2,23 @@ // 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.Collections.Generic; using Xunit; namespace System.Text.Json.Serialization.Tests { public static partial class ArrayTests { + [Fact] + public static void WriteEmpty() + { + string json = JsonSerializer.ToString(new SimpleTestClass[] { }); + Assert.Equal("[]", json); + + json = JsonSerializer.ToString(new List()); + Assert.Equal("[]", json); + } + [Fact] public static void WriteClassWithStringArray() { @@ -36,6 +47,31 @@ public static void WriteClassWithObjectArray() { string json; + { + TestClassWithObjectList obj = new TestClassWithObjectList(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectList obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectList obj = JsonSerializer.Parse(TestClassWithObjectList.s_data); + obj.Verify(); + } + } + + + + [Fact] + public static void WriteClassWithGenericList() + { + string json; + { TestClassWithGenericList obj = new TestClassWithGenericList(); obj.Initialize(); @@ -54,25 +90,231 @@ public static void WriteClassWithObjectArray() } } + public static void WriteClassWithGenericIEnumerableT() + { + string json; + + { + TestClassWithGenericIEnumerableT obj = new TestClassWithGenericIEnumerableT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithGenericIEnumerableT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithGenericIEnumerableT obj = JsonSerializer.Parse(TestClassWithGenericIEnumerableT.s_data); + obj.Verify(); + } + } + [Fact] - public static void WriteClassWithGenericList() + public static void WriteClassWithGenericIListT() { string json; { - TestClassWithObjectList obj = new TestClassWithObjectList(); + TestClassWithGenericIListT obj = new TestClassWithGenericIListT(); obj.Initialize(); obj.Verify(); json = JsonSerializer.ToString(obj); } { - TestClassWithObjectList obj = JsonSerializer.Parse(json); + TestClassWithGenericIListT obj = JsonSerializer.Parse(json); obj.Verify(); } { - TestClassWithObjectList obj = JsonSerializer.Parse(TestClassWithObjectList.s_data); + TestClassWithGenericIListT obj = JsonSerializer.Parse(TestClassWithGenericIListT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithGenericICollectionT() + { + string json; + + { + TestClassWithGenericICollectionT obj = new TestClassWithGenericICollectionT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithGenericICollectionT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithGenericICollectionT obj = JsonSerializer.Parse(TestClassWithGenericICollectionT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithGenericIReadOnlyCollectionT() + { + string json; + + { + TestClassWithGenericIReadOnlyCollectionT obj = new TestClassWithGenericIReadOnlyCollectionT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithGenericIReadOnlyCollectionT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithGenericIReadOnlyCollectionT obj = JsonSerializer.Parse(TestClassWithGenericIReadOnlyCollectionT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithGenericIReadOnlyListT() + { + string json; + + { + TestClassWithGenericIReadOnlyListT obj = new TestClassWithGenericIReadOnlyListT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithGenericIReadOnlyListT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithGenericIReadOnlyListT obj = JsonSerializer.Parse(TestClassWithGenericIEnumerableT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithObjectIEnumerableT() + { + string json; + + { + TestClassWithObjectIEnumerableT obj = new TestClassWithObjectIEnumerableT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectIEnumerableT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectIEnumerableT obj = JsonSerializer.Parse(TestClassWithObjectIEnumerableT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithObjectIListT() + { + string json; + + { + TestClassWithObjectIListT obj = new TestClassWithObjectIListT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectIListT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectIListT obj = JsonSerializer.Parse(TestClassWithObjectIListT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithObjectICollectionT() + { + string json; + + { + TestClassWithObjectICollectionT obj = new TestClassWithObjectICollectionT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectICollectionT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectICollectionT obj = JsonSerializer.Parse(TestClassWithObjectICollectionT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithObjectIReadOnlyCollectionT() + { + string json; + + { + TestClassWithObjectIReadOnlyCollectionT obj = new TestClassWithObjectIReadOnlyCollectionT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectIReadOnlyCollectionT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectIReadOnlyCollectionT obj = JsonSerializer.Parse(TestClassWithObjectIReadOnlyCollectionT.s_data); + obj.Verify(); + } + } + + [Fact] + public static void WriteClassWithObjectIReadOnlyListT() + { + string json; + + { + TestClassWithObjectIReadOnlyListT obj = new TestClassWithObjectIReadOnlyListT(); + obj.Initialize(); + obj.Verify(); + json = JsonSerializer.ToString(obj); + } + + { + TestClassWithObjectIReadOnlyListT obj = JsonSerializer.Parse(json); + obj.Verify(); + } + + { + TestClassWithObjectIReadOnlyListT obj = JsonSerializer.Parse(TestClassWithObjectIEnumerableT.s_data); obj.Verify(); } } diff --git a/src/System.Text.Json/tests/Serialization/CacheTests.cs b/src/System.Text.Json/tests/Serialization/CacheTests.cs index 9fe898837442..52a8811cefdf 100644 --- a/src/System.Text.Json/tests/Serialization/CacheTests.cs +++ b/src/System.Text.Json/tests/Serialization/CacheTests.cs @@ -9,42 +9,40 @@ namespace System.Text.Json.Serialization.Tests { public static class CacheTests { + // Use a new type that is not used in other tests so we can attempt race conditions on cached global state. + public class TestClassForCachingTest : SimpleTestClass { } + [Fact] - [ActiveIssue(36618)] public static void MultipleThreads() { - // Ensure no exceptions are thrown due to caching or other issues. - void SerializeAndDeserializeObject(bool useEmptyJson) + void DeserializeObjectFlipped() + { + TestClassForCachingTest obj = JsonSerializer.Parse(SimpleTestClass.s_json_flipped); + obj.Verify(); + }; + + void DeserializeObjectNormal() { - // Use localized caching - // Todo: localized caching not implemented yet. When implemented, add a run-time attribute to JsonSerializerOptions as that will create a separate cache held by JsonSerializerOptions. - var options = new JsonSerializerOptions(); - - string json; - - if (useEmptyJson) - { - json = "{}"; - } - else - { - SimpleTestClass testObj = new SimpleTestClass(); - testObj.Initialize(); - testObj.Verify(); - - json = JsonSerializer.ToString(testObj, options); - } - - SimpleTestClass testObjDeserialized = JsonSerializer.Parse(json, options); - testObjDeserialized.Verify(); + TestClassForCachingTest obj = JsonSerializer.Parse(SimpleTestClass.s_json); + obj.Verify(); }; - Task[] tasks = new Task[8]; - bool useEmptyJson = false; - for (int i = 0; i < tasks.Length; i++) + void SerializeObject() { - tasks[i] = Task.Run(() => SerializeAndDeserializeObject(useEmptyJson)); - useEmptyJson = !useEmptyJson; + var obj = new TestClassForCachingTest(); + obj.Initialize(); + JsonSerializer.ToString(obj); + }; + + Task[] tasks = new Task[4 * 3]; + for (int i = 0; i < tasks.Length; i += 3) + { + // Create race condition to populate the sorted property cache with different json ordering. + tasks[i + 0] = Task.Run(() => DeserializeObjectFlipped()); + tasks[i + 1] = Task.Run(() => DeserializeObjectNormal()); + + // Ensure no exceptions on serialization + tasks[i + 2] = Task.Run(() => SerializeObject()); }; Task.WaitAll(tasks); diff --git a/src/System.Text.Json/tests/Serialization/CamelCaseUnitTests.cs b/src/System.Text.Json/tests/Serialization/CamelCaseUnitTests.cs new file mode 100644 index 000000000000..ed6a6d9cc856 --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/CamelCaseUnitTests.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. +// See the LICENSE file in the project root for more information. + +using System.Reflection; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static class CamelCaseUnitTests + { + [Fact] + public static void ToCamelCaseTest() + { + // These test cases were copied from Json.NET. + Assert.Equal("urlValue", ConvertToCamelCase("URLValue")); + Assert.Equal("url", ConvertToCamelCase("URL")); + Assert.Equal("id", ConvertToCamelCase("ID")); + Assert.Equal("i", ConvertToCamelCase("I")); + Assert.Equal("", ConvertToCamelCase("")); + Assert.Equal(null, ConvertToCamelCase(null)); + Assert.Equal("person", ConvertToCamelCase("Person")); + Assert.Equal("iPhone", ConvertToCamelCase("iPhone")); + Assert.Equal("iPhone", ConvertToCamelCase("IPhone")); + Assert.Equal("i Phone", ConvertToCamelCase("I Phone")); + Assert.Equal("i Phone", ConvertToCamelCase("I Phone")); + Assert.Equal(" IPhone", ConvertToCamelCase(" IPhone")); + Assert.Equal(" IPhone ", ConvertToCamelCase(" IPhone ")); + Assert.Equal("isCIA", ConvertToCamelCase("IsCIA")); + Assert.Equal("vmQ", ConvertToCamelCase("VmQ")); + Assert.Equal("xml2Json", ConvertToCamelCase("Xml2Json")); + Assert.Equal("snAkEcAsE", ConvertToCamelCase("SnAkEcAsE")); + Assert.Equal("snA__kEcAsE", ConvertToCamelCase("SnA__kEcAsE")); + Assert.Equal("snA__ kEcAsE", ConvertToCamelCase("SnA__ kEcAsE")); + Assert.Equal("already_snake_case_ ", ConvertToCamelCase("already_snake_case_ ")); + Assert.Equal("isJSONProperty", ConvertToCamelCase("IsJSONProperty")); + Assert.Equal("shoutinG_CASE", ConvertToCamelCase("SHOUTING_CASE")); + Assert.Equal("9999-12-31T23:59:59.9999999Z", ConvertToCamelCase("9999-12-31T23:59:59.9999999Z")); + Assert.Equal("hi!! This is text. Time to test.", ConvertToCamelCase("Hi!! This is text. Time to test.")); + Assert.Equal("building", ConvertToCamelCase("BUILDING")); + Assert.Equal("building Property", ConvertToCamelCase("BUILDING Property")); + Assert.Equal("building Property", ConvertToCamelCase("Building Property")); + Assert.Equal("building PROPERTY", ConvertToCamelCase("BUILDING PROPERTY")); + } + + // Use a helper method since the method is not public. + private static string ConvertToCamelCase(string name) + { + JsonNamingPolicy policy = JsonNamingPolicy.CamelCase; + string value = policy.ConvertName(name); + return value; + } + } +} diff --git a/src/System.Text.Json/tests/Serialization/Null.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Null.ReadTests.cs index b36ad309170e..13bb1d5335aa 100644 --- a/src/System.Text.Json/tests/Serialization/Null.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Null.ReadTests.cs @@ -30,20 +30,20 @@ public static void RootObjectIsNull() } [Fact] - public static void DefaultReadValue() + public static void DefaultIgnoreNullValuesOnRead() { - TestClassWithNullButInitialized obj = JsonSerializer.Parse(TestClassWithNullButInitialized.s_json); + TestClassWithInitializedProperties obj = JsonSerializer.Parse(TestClassWithInitializedProperties.s_null_json); Assert.Equal(null, obj.MyString); Assert.Equal(null, obj.MyInt); } [Fact] - public static void OverrideReadOnOption() + public static void EnableIgnoreNullValuesOnRead() { var options = new JsonSerializerOptions(); - options.IgnoreNullPropertyValueOnRead = true; + options.IgnoreNullValues = true; - TestClassWithNullButInitialized obj = JsonSerializer.Parse(TestClassWithNullButInitialized.s_json, options); + TestClassWithInitializedProperties obj = JsonSerializer.Parse(TestClassWithInitializedProperties.s_null_json, options); Assert.Equal("Hello", obj.MyString); Assert.Equal(1, obj.MyInt); } diff --git a/src/System.Text.Json/tests/Serialization/Null.WriteTests.cs b/src/System.Text.Json/tests/Serialization/Null.WriteTests.cs index 6dc32aaf0360..8e72f1e5c6a8 100644 --- a/src/System.Text.Json/tests/Serialization/Null.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Null.WriteTests.cs @@ -9,21 +9,28 @@ namespace System.Text.Json.Serialization.Tests public static partial class NullTests { [Fact] - public static void DefaultWriteOptions() + public static void DefaultIgnoreNullValuesOnWrite() { - var input = new TestClassWithNull(); - string json = JsonSerializer.ToString(input); - Assert.Equal(@"{""MyString"":null}", json); + var obj = new TestClassWithInitializedProperties(); + obj.MyString = null; + obj.MyInt = null; + + string json = JsonSerializer.ToString(obj); + Assert.Contains(@"""MyString"":null", json); + Assert.Contains(@"""MyInt"":null", json); } [Fact] - public static void OverrideWriteOnOption() + public static void EnableIgnoreNullValuesOnWrite() { JsonSerializerOptions options = new JsonSerializerOptions(); - options.IgnoreNullPropertyValueOnWrite = true; + options.IgnoreNullValues = true; + + var obj = new TestClassWithInitializedProperties(); + obj.MyString = null; + obj.MyInt = null; - var input = new TestClassWithNull(); - string json = JsonSerializer.ToString(input, options); + string json = JsonSerializer.ToString(obj, options); Assert.Equal(@"{}", json); } @@ -34,6 +41,11 @@ public static void NullReferences() obj.Address = null; obj.Array = null; obj.List = null; + obj.IEnumerableT = null; + obj.IListT = null; + obj.ICollectionT = null; + obj.IReadOnlyCollectionT = null; + obj.IReadOnlyListT = null; obj.NullableInt = null; obj.NullableIntArray = null; obj.Object = null; @@ -42,6 +54,11 @@ public static void NullReferences() Assert.Contains(@"""Address"":null", json); Assert.Contains(@"""List"":null", json); Assert.Contains(@"""Array"":null", json); + Assert.Contains(@"""IEnumerableT"":null", json); + Assert.Contains(@"""IListT"":null", json); + Assert.Contains(@"""ICollectionT"":null", json); + Assert.Contains(@"""IReadOnlyCollectionT"":null", json); + Assert.Contains(@"""IReadOnlyListT"":null", json); Assert.Contains(@"""NullableInt"":null", json); Assert.Contains(@"""Object"":null", json); Assert.Contains(@"""NullableIntArray"":null", json); diff --git a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs index 88637d838a33..ae498e5965be 100644 --- a/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Object.ReadTests.cs @@ -15,6 +15,13 @@ public static void ReadSimpleClass() obj.Verify(); } + [Fact] + public static void ReadEmpty() + { + SimpleTestClass obj = JsonSerializer.Parse("{}"); + Assert.NotNull(obj); + } + [Fact] public static void EmptyClassWithRandomData() { diff --git a/src/System.Text.Json/tests/Serialization/OptionsTests.cs b/src/System.Text.Json/tests/Serialization/OptionsTests.cs index 16031f69dfea..be416b02c6d2 100644 --- a/src/System.Text.Json/tests/Serialization/OptionsTests.cs +++ b/src/System.Text.Json/tests/Serialization/OptionsTests.cs @@ -8,6 +8,36 @@ namespace System.Text.Json.Serialization.Tests { public static partial class OptionsTests { + [Fact] + public static void SetOptionsFail() + { + var options = new JsonSerializerOptions(); + + JsonSerializer.Parse("1", options); + + // Verify defaults and ensure getters do not throw. + Assert.False(options.AllowTrailingCommas); + Assert.Equal(16 * 1024, options.DefaultBufferSize); + Assert.Equal(null, options.DictionaryKeyPolicy); + Assert.False(options.IgnoreNullValues); + Assert.Equal(0, options.MaxDepth); + Assert.Equal(false, options.PropertyNameCaseInsensitive); + Assert.Equal(null, options.PropertyNamingPolicy); + Assert.Equal(JsonCommentHandling.Disallow, options.ReadCommentHandling); + Assert.False(options.WriteIndented); + + // Setters should always throw; we don't check to see if the value is the same or not. + Assert.Throws(() => options.AllowTrailingCommas = options.AllowTrailingCommas); + Assert.Throws(() => options.DefaultBufferSize = options.DefaultBufferSize); + Assert.Throws(() => options.DictionaryKeyPolicy = options.DictionaryKeyPolicy); + Assert.Throws(() => options.IgnoreNullValues = options.IgnoreNullValues); + Assert.Throws(() => options.MaxDepth = options.MaxDepth); + Assert.Throws(() => options.PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive); + Assert.Throws(() => options.PropertyNamingPolicy = options.PropertyNamingPolicy); + Assert.Throws(() => options.ReadCommentHandling = options.ReadCommentHandling); + Assert.Throws(() => options.WriteIndented = options.WriteIndented); + } + [Fact] public static void DefaultBufferSizeFail() { @@ -25,5 +55,69 @@ public static void DefaultBufferSize() options.DefaultBufferSize = 1; Assert.Equal(1, options.DefaultBufferSize); } + + [Fact] + public static void AllowTrailingCommas() + { + Assert.Throws(() => JsonSerializer.Parse("[1,]")); + + var options = new JsonSerializerOptions(); + options.AllowTrailingCommas = true; + + int[] value = JsonSerializer.Parse("[1,]", options); + Assert.Equal(1, value[0]); + } + + [Fact] + public static void WriteIndented() + { + var obj = new BasicCompany(); + obj.Initialize(); + + // Verify default value. + string json = JsonSerializer.ToString(obj); + Assert.DoesNotContain(Environment.NewLine, json); + + // Verify default value on options. + var options = new JsonSerializerOptions(); + json = JsonSerializer.ToString(obj, options); + Assert.DoesNotContain(Environment.NewLine, json); + + // Change the value on options. + options = new JsonSerializerOptions(); + options.WriteIndented = true; + json = JsonSerializer.ToString(obj, options); + Assert.Contains(Environment.NewLine, json); + } + + [Fact] + public static void ReadCommentHandling() + { + Assert.Throws(() => JsonSerializer.Parse("/* commment */")); + + var options = new JsonSerializerOptions(); + + Assert.Throws(() => JsonSerializer.Parse("/* commment */", options)); + + options = new JsonSerializerOptions(); + options.ReadCommentHandling = JsonCommentHandling.Allow; + + JsonSerializer.Parse("/* commment */", options); + } + + [Fact] + public static void MaxDepthRead() + { + JsonSerializer.Parse(BasicCompany.s_data); + + var options = new JsonSerializerOptions(); + + JsonSerializer.Parse(BasicCompany.s_data, options); + + options = new JsonSerializerOptions(); + options.MaxDepth = 1; + + Assert.Throws(() => JsonSerializer.Parse(BasicCompany.s_data, options)); + } } } diff --git a/src/System.Text.Json/tests/Serialization/PolymorphicTests.cs b/src/System.Text.Json/tests/Serialization/PolymorphicTests.cs index 0f036efc38b1..3c9ac76f1aa2 100644 --- a/src/System.Text.Json/tests/Serialization/PolymorphicTests.cs +++ b/src/System.Text.Json/tests/Serialization/PolymorphicTests.cs @@ -75,6 +75,41 @@ public static void ArrayAsRootObject() json = JsonSerializer.ToString(list); Assert.Equal(ExpectedJson, json); + + IEnumerable ienumerable = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(ienumerable); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(ienumerable); + Assert.Equal(ExpectedJson, json); + + IList ilist = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(ilist); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(ilist); + Assert.Equal(ExpectedJson, json); + + ICollection icollection = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(icollection); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(icollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyCollection ireadonlycollection = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(ireadonlycollection); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(ireadonlycollection); + Assert.Equal(ExpectedJson, json); + + IReadOnlyList ireadonlylist = new List { 1, true, address, null, "foo" }; + json = JsonSerializer.ToString(ireadonlylist); + Assert.Equal(ExpectedJson, json); + + json = JsonSerializer.ToString(ireadonlylist); + Assert.Equal(ExpectedJson, json); } [Fact] @@ -109,6 +144,11 @@ static void Verify(string json) Assert.Contains(@"""Address"":{""City"":""MyCity""}", json); Assert.Contains(@"""List"":[""Hello"",""World""]", json); Assert.Contains(@"""Array"":[""Hello"",""Again""]", json); + Assert.Contains(@"""IEnumerableT"":[""Hello"",""World""]", json); + Assert.Contains(@"""IListT"":[""Hello"",""World""]", json); + Assert.Contains(@"""ICollectionT"":[""Hello"",""World""]", json); + Assert.Contains(@"""IReadOnlyCollectionT"":[""Hello"",""World""]", json); + Assert.Contains(@"""IReadOnlyListT"":[""Hello"",""World""]", json); Assert.Contains(@"""NullableInt"":42", json); Assert.Contains(@"""Object"":{}", json); Assert.Contains(@"""NullableIntArray"":[null,42,null]", json); @@ -141,7 +181,7 @@ public static void NestedObjectAsRootObjectIgnoreNullable() Assert.Contains(@"""NullableInt"":null", json); JsonSerializerOptions options = new JsonSerializerOptions(); - options.IgnoreNullPropertyValueOnWrite = true; + options.IgnoreNullValues = true; json = JsonSerializer.ToString(obj, options); Assert.DoesNotContain(@"""NullableInt"":null", json); } diff --git a/src/System.Text.Json/tests/Serialization/PropertyNameTests.cs b/src/System.Text.Json/tests/Serialization/PropertyNameTests.cs new file mode 100644 index 000000000000..0bb3ba169d7a --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/PropertyNameTests.cs @@ -0,0 +1,288 @@ +// 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 Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public static class PropertyNameTests + { + [Fact] + public static void CamelCaseDeserializeNoMatch() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + SimpleTestClass obj = JsonSerializer.Parse(@"{""MyInt16"":1}", options); + + // This is 0 (default value) because the data does not match the property "MyInt16" that is assuming camel-casing of "myInt16". + Assert.Equal(0, obj.MyInt16); + } + + [Fact] + public static void CamelCaseDeserializeMatch() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + SimpleTestClass obj = JsonSerializer.Parse(@"{""myInt16"":1}", options); + + // This is 1 because the data matches the property "MyInt16" that is assuming camel-casing of "myInt16". + Assert.Equal(1, obj.MyInt16); + } + + [Fact] + public static void CamelCaseSerialize() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + SimpleTestClass obj = JsonSerializer.Parse(@"{}", options); + string json = JsonSerializer.ToString(obj, options); + Assert.Contains(@"""myInt16"":0", json); + Assert.Contains(@"""myInt32"":0", json); + } + + [Fact] + public static void CustomNamePolicy() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = new UppercaseNamingPolicy(); + + SimpleTestClass obj = JsonSerializer.Parse(@"{""MYINT16"":1}", options); + + // This is 1 because the data matches the property "MYINT16" that is uppercase of "myInt16". + Assert.Equal(1, obj.MyInt16); + } + + [Fact] + public static void NullNamePolicy() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = new NullNamingPolicy(); + + // A policy that returns null is not allowed. + Assert.Throws(() => JsonSerializer.Parse(@"{}", options)); + } + + [Fact] + public static void IgnoreCase() + { + { + // A non-match scenario with no options (case-sensitive by default). + SimpleTestClass obj = JsonSerializer.Parse(@"{""myint16"":1}"); + Assert.Equal(0, obj.MyInt16); + } + + { + // A non-match scenario with default options (case-sensitive by default). + var options = new JsonSerializerOptions(); + SimpleTestClass obj = JsonSerializer.Parse(@"{""myint16"":1}", options); + Assert.Equal(0, obj.MyInt16); + } + + { + var options = new JsonSerializerOptions(); + options.PropertyNameCaseInsensitive = true; + SimpleTestClass obj = JsonSerializer.Parse(@"{""myint16"":1}", options); + Assert.Equal(1, obj.MyInt16); + } + } + + [Fact] + public static void JsonPropertyNameAttribute() + { + { + OverridePropertyNameDesignTime_TestClass obj = JsonSerializer.Parse(@"{""Blah"":1}"); + Assert.Equal(1, obj.myInt); + + obj.myObject = 2; + + string json = JsonSerializer.ToString(obj); + Assert.Contains(@"""Blah"":1", json); + Assert.Contains(@"""BlahObject"":2", json); + } + + // The JsonPropertyNameAttribute should be unaffected by JsonNamingPolicy and PropertyNameCaseInsensitive. + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.PropertyNameCaseInsensitive = true; + + OverridePropertyNameDesignTime_TestClass obj = JsonSerializer.Parse(@"{""Blah"":1}", options); + Assert.Equal(1, obj.myInt); + + string json = JsonSerializer.ToString(obj); + Assert.Contains(@"""Blah"":1", json); + } + } + + [Fact] + public static void JsonNameAttributeDuplicateDesignTimeFail() + { + { + var options = new JsonSerializerOptions(); + Assert.Throws(() => JsonSerializer.Parse("{}", options)); + } + + { + var options = new JsonSerializerOptions(); + Assert.Throws(() => JsonSerializer.ToString(new DuplicatePropertyNameDesignTime_TestClass(), options)); + } + } + + [Fact] + public static void JsonNullNameAttribute() + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.PropertyNameCaseInsensitive = true; + + // A null name in JsonPropertyNameAttribute is not allowed. + Assert.Throws(() => JsonSerializer.ToString(new NullPropertyName_TestClass(), options)); + } + + [Fact] + public static void JsonNameConflictOnCamelCasingFail() + { + { + // Baseline comparison - no options set. + IntPropertyNamesDifferentByCaseOnly_TestClass obj = JsonSerializer.Parse("{}"); + JsonSerializer.ToString(obj); + } + + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + Assert.Throws(() => JsonSerializer.Parse("{}", options)); + Assert.Throws(() => JsonSerializer.ToString(new IntPropertyNamesDifferentByCaseOnly_TestClass(), options)); + } + + { + // Baseline comparison - no options set. + ObjectPropertyNamesDifferentByCaseOnly_TestClass obj = JsonSerializer.Parse("{}"); + JsonSerializer.ToString(obj); + } + + { + var options = new JsonSerializerOptions(); + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + + Assert.Throws(() => JsonSerializer.Parse("{}", options)); + Assert.Throws(() => JsonSerializer.ToString(new ObjectPropertyNamesDifferentByCaseOnly_TestClass(), options)); + } + } + + [Fact] + public static void JsonNameConflictOnCaseInsensitiveFail() + { + string json = @"{""myInt"":1,""MyInt"":2}"; + + { + var options = new JsonSerializerOptions(); + options.PropertyNameCaseInsensitive = true; + + Assert.Throws(() => JsonSerializer.Parse(json, options)); + Assert.Throws(() => JsonSerializer.ToString(new IntPropertyNamesDifferentByCaseOnly_TestClass(), options)); + } + } + + [Fact] + public static void JsonOutputNotAffectedByCasingPolicy() + { + { + // Baseline. + string json = JsonSerializer.ToString(new SimpleTestClass()); + Assert.Contains(@"""MyInt16"":0", json); + } + + // The JSON output should be unaffected by PropertyNameCaseInsensitive. + { + var options = new JsonSerializerOptions(); + options.PropertyNameCaseInsensitive = true; + + string json = JsonSerializer.ToString(new SimpleTestClass(), options); + Assert.Contains(@"""MyInt16"":0", json); + } + } + + [Fact] + public static void EmptyPropertyName() + { + string json = @"{"""":1}"; + + { + var obj = new EmptyPropertyName_TestClass(); + obj.MyInt1 = 1; + + string jsonOut = JsonSerializer.ToString(obj); + Assert.Equal(json, jsonOut); + } + + { + EmptyPropertyName_TestClass obj = JsonSerializer.Parse(json); + Assert.Equal(1, obj.MyInt1); + } + } + } + + public class OverridePropertyNameDesignTime_TestClass + { + [JsonPropertyName("Blah")] + public int myInt { get; set; } + + [JsonPropertyName("BlahObject")] + public object myObject { get; set; } + } + + public class DuplicatePropertyNameDesignTime_TestClass + { + [JsonPropertyName("Blah")] + public int MyInt1 { get; set; } + + [JsonPropertyName("Blah")] + public int MyInt2 { get; set; } + } + + public class EmptyPropertyName_TestClass + { + [JsonPropertyName("")] + public int MyInt1 { get; set; } + } + + public class NullPropertyName_TestClass + { + [JsonPropertyName(null)] + public int MyInt1 { get; set; } + } + + public class IntPropertyNamesDifferentByCaseOnly_TestClass + { + public int myInt { get; set; } + public int MyInt { get; set; } + } + + public class ObjectPropertyNamesDifferentByCaseOnly_TestClass + { + public int myObject { get; set; } + public int MyObject { get; set; } + } + + public class UppercaseNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + return name.ToUpperInvariant(); + } + } + + public class NullNamingPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) + { + return null; + } + } +} diff --git a/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs b/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs index 97ba2e6f7c2c..dd4f8ecbb5d3 100644 --- a/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs +++ b/src/System.Text.Json/tests/Serialization/PropertyVisibilityTests.cs @@ -2,6 +2,7 @@ // 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.Collections.Generic; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -14,22 +15,37 @@ public static void NoSetter() var obj = new ClassWithNoSetter(); string json = JsonSerializer.ToString(obj); - Assert.Equal(@"{""MyString"":""DefaultValue""}", json); + Assert.Contains(@"""MyString"":""DefaultValue""", json); + Assert.Contains(@"""MyInts"":[1,2]", json); - ClassWithNoSetter objCopy = JsonSerializer.Parse(json); - Assert.Equal("DefaultValue", objCopy.MyString); + obj = JsonSerializer.Parse(@"{""MyString"":""IgnoreMe"",""MyInts"":[0]}"); + Assert.Equal("DefaultValue", obj.MyString); + Assert.Equal(2, obj.MyInts.Length); + } + + [Fact] + public static void IgnoreReadOnlyProperties() + { + var options = new JsonSerializerOptions(); + options.IgnoreReadOnlyProperties = true; + + var obj = new ClassWithNoSetter(); + + string json = JsonSerializer.ToString(obj, options); + Assert.Equal(@"{}", json); } [Fact] public static void NoGetter() { - var objNoSetter = new ClassWithNoSetter(); + ClassWithNoGetter objWithNoGetter = JsonSerializer.Parse( + @"{""MyString"":""Hello"",""MyIntArray"":[0],""MyIntList"":[0]}"); - string json = JsonSerializer.ToString(objNoSetter); - Assert.Equal(@"{""MyString"":""DefaultValue""}", json); + Assert.Equal("Hello", objWithNoGetter.GetMyString()); - ClassWithNoGetter objNoGetter = JsonSerializer.Parse(json); - Assert.Equal("DefaultValue", objNoGetter.GetMyString()); + // Currently we don't support setters without getters. + Assert.Equal(0, objWithNoGetter.GetMyIntArray().Length); + Assert.Equal(0, objWithNoGetter.GetMyIntList().Count); } [Fact] @@ -51,21 +67,69 @@ public static void PrivateSetter() Assert.Null(objCopy.GetMyString()); } + [Fact] + public static void JsonIgnoreAttribute() + { + // Verify default state. + var obj = new ClassWithIgnoreAttributeProperty(); + Assert.Equal(@"MyString", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + + // Verify serialize. + string json = JsonSerializer.ToString(obj); + Assert.Contains(@"""MyString""", json); + Assert.DoesNotContain(@"MyStringWithIgnore", json); + Assert.DoesNotContain(@"MyStringsWithIgnore", json); + + // Verify deserialize default. + obj = JsonSerializer.Parse(@"{}"); + Assert.Equal(@"MyString", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + + // Verify deserialize ignores the json for MyStringWithIgnore and MyStringsWithIgnore. + obj = JsonSerializer.Parse( + @"{""MyString"":""Hello"", ""MyStringWithIgnore"":""IgnoreMe"", ""MyStringsWithIgnore"":[""IgnoreMe""]}"); + Assert.Contains(@"Hello", obj.MyString); + Assert.Equal(@"MyStringWithIgnore", obj.MyStringWithIgnore); + Assert.Equal(2, obj.MyStringsWithIgnore.Length); + } + // Todo: add tests with missing object property and missing collection property. + public class ClassWithPrivateSetterAndGetter + { + private string MyString { get; set; } + + public string GetMyString() + { + return MyString; + } + + public void SetMyString(string value) + { + MyString = value; + } + } + public class ClassWithNoSetter { public ClassWithNoSetter() { MyString = "DefaultValue"; + MyInts = new int[] { 1, 2 }; } public string MyString { get; } + public int[] MyInts { get; } } public class ClassWithNoGetter { string _myString = ""; + int[] _myIntArray = new int[] { }; + List _myIntList = new List { }; public string MyString { @@ -75,25 +139,54 @@ public string MyString } } + public int[] MyIntArray + { + set + { + _myIntArray = value; + } + } + + public List MyList + { + set + { + _myIntList = value; + } + } + public string GetMyString() { return _myString; } - } - public class ClassWithPrivateSetterAndGetter - { - private string MyString { get; set; } + public int[] GetMyIntArray() + { + return _myIntArray; + } - public string GetMyString() + public List GetMyIntList() { - return MyString; + return _myIntList; } + } - public void SetMyString(string value) + public class ClassWithIgnoreAttributeProperty + { + public ClassWithIgnoreAttributeProperty() { - MyString = value; + MyString = "MyString"; + MyStringWithIgnore = "MyStringWithIgnore"; + MyStringsWithIgnore = new string[] { "1", "2" }; } + + [JsonIgnore] + public string MyStringWithIgnore { get; set; } + + public string MyString { get; set; } + + [JsonIgnore] + public string[] MyStringsWithIgnore { get; set; } } } } diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs b/src/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs index c55f43f7a6ba..f97e14c28511 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.Polymorphic.cs @@ -110,6 +110,11 @@ public class ObjectWithObjectProperties public object /*Address*/ Address { get; set; } public object /*List*/ List { get; set; } public object /*string[]*/ Array { get; set; } + public object /*IEnumerable*/ IEnumerableT { get; set; } + public object /*IList*/ IListT { get; set; } + public object /*ICollection*/ ICollectionT { get; set; } + public object /*IReadOnlyCollection*/ IReadOnlyCollectionT { get; set; } + public object /*IReadOnlyList*/ IReadOnlyListT { get; set; } public object /*int?*/ NullableInt { get; set; } public object /*object*/ Object { get; set; } public object /*int?[]*/ NullableIntArray { get; set; } @@ -129,6 +134,31 @@ public ObjectWithObjectProperties() "Hello", "Again" }; + IEnumerableT = new List + { + "Hello", "World" + }; + + IListT = new List + { + "Hello", "World" + }; + + ICollectionT = new List + { + "Hello", "World" + }; + + IReadOnlyCollectionT = new List + { + "Hello", "World" + }; + + IReadOnlyListT = new List + { + "Hello", "World" + }; + NullableInt = new int?(42); Object = new object(); NullableIntArray = new int?[] { null, 42, null }; diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs new file mode 100644 index 000000000000..ba35451c3f7f --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClass.cs @@ -0,0 +1,203 @@ +// 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.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public class SimpleTestClass : ITestClass + { + public short MyInt16 { get; set; } + public int MyInt32 { get; set; } + public long MyInt64 { get; set; } + public ushort MyUInt16 { get; set; } + public uint MyUInt32 { get; set; } + public ulong MyUInt64 { get; set; } + public byte MyByte { get; set; } + public sbyte MySByte { get; set; } + public char MyChar { get; set; } + public string MyString { get; set; } + public decimal MyDecimal { get; set; } + public bool MyBooleanTrue { get; set; } + public bool MyBooleanFalse { get; set; } + public float MySingle { get; set; } + public double MyDouble { get; set; } + public DateTime MyDateTime { get; set; } + public DateTimeOffset MyDateTimeOffset { get; set; } + public SampleEnum MyEnum { get; set; } + public short[] MyInt16Array { get; set; } + public int[] MyInt32Array { get; set; } + public long[] MyInt64Array { get; set; } + public ushort[] MyUInt16Array { get; set; } + public uint[] MyUInt32Array { get; set; } + public ulong[] MyUInt64Array { get; set; } + public byte[] MyByteArray { get; set; } + public sbyte[] MySByteArray { get; set; } + public char[] MyCharArray { get; set; } + public string[] MyStringArray { get; set; } + public decimal[] MyDecimalArray { get; set; } + public bool[] MyBooleanTrueArray { get; set; } + public bool[] MyBooleanFalseArray { get; set; } + public float[] MySingleArray { get; set; } + public double[] MyDoubleArray { get; set; } + public DateTime[] MyDateTimeArray { get; set; } + public DateTimeOffset[] MyDateTimeOffsetArray { get; set; } + public SampleEnum[] MyEnumArray { get; set; } + public List MyStringList { get; set; } + public IEnumerable MyStringIEnumerableT { get; set; } + public IList MyStringIListT { get; set; } + public ICollection MyStringICollectionT { get; set; } + public IReadOnlyCollection MyStringIReadOnlyCollectionT { get; set; } + public IReadOnlyList MyStringIReadOnlyListT { get; set; } + + public static readonly string s_json = $"{{{s_partialJsonProperties},{s_partialJsonArrays}}}"; + public static readonly string s_json_flipped = $"{{{s_partialJsonArrays},{s_partialJsonProperties}}}"; + + private const string s_partialJsonProperties = + @"""MyInt16"" : 1," + + @"""MyInt32"" : 2," + + @"""MyInt64"" : 3," + + @"""MyUInt16"" : 4," + + @"""MyUInt32"" : 5," + + @"""MyUInt64"" : 6," + + @"""MyByte"" : 7," + + @"""MySByte"" : 8," + + @"""MyChar"" : ""a""," + + @"""MyString"" : ""Hello""," + + @"""MyBooleanTrue"" : true," + + @"""MyBooleanFalse"" : false," + + @"""MySingle"" : 1.1," + + @"""MyDouble"" : 2.2," + + @"""MyDecimal"" : 3.3," + + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + + @"""MyEnum"" : 2"; // int by default + + private const string s_partialJsonArrays = + @"""MyInt16Array"" : [1]," + + @"""MyInt32Array"" : [2]," + + @"""MyInt64Array"" : [3]," + + @"""MyUInt16Array"" : [4]," + + @"""MyUInt32Array"" : [5]," + + @"""MyUInt64Array"" : [6]," + + @"""MyByteArray"" : [7]," + + @"""MySByteArray"" : [8]," + + @"""MyCharArray"" : [""a""]," + + @"""MyStringArray"" : [""Hello""]," + + @"""MyBooleanTrueArray"" : [true]," + + @"""MyBooleanFalseArray"" : [false]," + + @"""MySingleArray"" : [1.1]," + + @"""MyDoubleArray"" : [2.2]," + + @"""MyDecimalArray"" : [3.3]," + + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + + @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + + @"""MyEnumArray"" : [2]," + // int by default + @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerableT"" : [""Hello""]," + + @"""MyStringIListT"" : [""Hello""]," + + @"""MyStringICollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyListT"" : [""Hello""]"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + public void Initialize() + { + MyInt16 = 1; + MyInt32 = 2; + MyInt64 = 3; + MyUInt16 = 4; + MyUInt32 = 5; + MyUInt64 = 6; + MyByte = 7; + MySByte = 8; + MyChar = 'a'; + MyString = "Hello"; + MyBooleanTrue = true; + MyBooleanFalse = false; + MySingle = 1.1f; + MyDouble = 2.2d; + MyDecimal = 3.3m; + MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); + MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); + MyEnum = SampleEnum.Two; + + MyInt16Array = new short[] { 1 }; + MyInt32Array = new int[] { 2 }; + MyInt64Array = new long[] { 3 }; + MyUInt16Array = new ushort[] { 4 }; + MyUInt32Array = new uint[] { 5 }; + MyUInt64Array = new ulong[] { 6 }; + MyByteArray = new byte[] { 7 }; + MySByteArray = new sbyte[] { 8 }; + MyCharArray = new char[] { 'a' }; + MyStringArray = new string[] { "Hello" }; + MyBooleanTrueArray = new bool[] { true }; + MyBooleanFalseArray = new bool[] { false }; + MySingleArray = new float[] { 1.1f }; + MyDoubleArray = new double[] { 2.2d }; + MyDecimalArray = new decimal[] { 3.3m }; + MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; + MyDateTimeOffsetArray = new DateTimeOffset[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; + MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + + MyStringList = new List() { "Hello" }; + MyStringIEnumerableT = new string[] { "Hello" }; + MyStringIListT = new string[] { "Hello" }; + MyStringICollectionT = new string[] { "Hello" }; + MyStringIReadOnlyCollectionT = new string[] { "Hello" }; + MyStringIReadOnlyListT = new string[] { "Hello" }; + } + + public void Verify() + { + Assert.Equal((short)1, MyInt16); + Assert.Equal((int)2, MyInt32); + Assert.Equal((long)3, MyInt64); + Assert.Equal((ushort)4, MyUInt16); + Assert.Equal((uint)5, MyUInt32); + Assert.Equal((ulong)6, MyUInt64); + Assert.Equal((byte)7, MyByte); + Assert.Equal((sbyte)8, MySByte); + Assert.Equal('a', MyChar); + Assert.Equal("Hello", MyString); + Assert.Equal(3.3m, MyDecimal); + Assert.Equal(false, MyBooleanFalse); + Assert.Equal(true, MyBooleanTrue); + Assert.Equal(1.1f, MySingle); + Assert.Equal(2.2d, MyDouble); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffset); + Assert.Equal(SampleEnum.Two, MyEnum); + + Assert.Equal((short)1, MyInt16Array[0]); + Assert.Equal((int)2, MyInt32Array[0]); + Assert.Equal((long)3, MyInt64Array[0]); + Assert.Equal((ushort)4, MyUInt16Array[0]); + Assert.Equal((uint)5, MyUInt32Array[0]); + Assert.Equal((ulong)6, MyUInt64Array[0]); + Assert.Equal((byte)7, MyByteArray[0]); + Assert.Equal((sbyte)8, MySByteArray[0]); + Assert.Equal('a', MyCharArray[0]); + Assert.Equal("Hello", MyStringArray[0]); + Assert.Equal(3.3m, MyDecimalArray[0]); + Assert.Equal(false, MyBooleanFalseArray[0]); + Assert.Equal(true, MyBooleanTrueArray[0]); + Assert.Equal(1.1f, MySingleArray[0]); + Assert.Equal(2.2d, MyDoubleArray[0]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); + Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + + Assert.Equal("Hello", MyStringList[0]); + Assert.Equal("Hello", MyStringIEnumerableT.First()); + Assert.Equal("Hello", MyStringIListT[0]); + Assert.Equal("Hello", MyStringICollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyCollectionT.First()); + Assert.Equal("Hello", MyStringIReadOnlyListT[0]); + } + } +} diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs new file mode 100644 index 000000000000..ac6953da1edd --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithNullables.cs @@ -0,0 +1,252 @@ +// 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 Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public abstract class SimpleBaseClassWithNullables + { + public short? MyInt16 { get; set; } + public int? MyInt32 { get; set; } + public long? MyInt64 { get; set; } + public ushort? MyUInt16 { get; set; } + public uint? MyUInt32 { get; set; } + public ulong? MyUInt64 { get; set; } + public byte? MyByte { get; set; } + public sbyte? MySByte { get; set; } + public char? MyChar { get; set; } + public decimal? MyDecimal { get; set; } + public bool? MyBooleanTrue { get; set; } + public bool? MyBooleanFalse { get; set; } + public float? MySingle { get; set; } + public double? MyDouble { get; set; } + public DateTime? MyDateTime { get; set; } + public DateTimeOffset? MyDateTimeOffset { get; set; } + public SampleEnum? MyEnum { get; set; } + public short?[] MyInt16Array { get; set; } + public int?[] MyInt32Array { get; set; } + public long?[] MyInt64Array { get; set; } + public ushort?[] MyUInt16Array { get; set; } + public uint?[] MyUInt32Array { get; set; } + public ulong?[] MyUInt64Array { get; set; } + public byte?[] MyByteArray { get; set; } + public sbyte?[] MySByteArray { get; set; } + public char?[] MyCharArray { get; set; } + public decimal?[] MyDecimalArray { get; set; } + public bool?[] MyBooleanTrueArray { get; set; } + public bool?[] MyBooleanFalseArray { get; set; } + public float?[] MySingleArray { get; set; } + public double?[] MyDoubleArray { get; set; } + public DateTime?[] MyDateTimeArray { get; set; } + public DateTimeOffset?[] MyDateTimeOffsetArray { get; set; } + public SampleEnum?[] MyEnumArray { get; set; } + } + + public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass + { + public void Initialize() + { + } + + public void Verify() + { + Assert.Null(MyInt16); + Assert.Null(MyInt32); + Assert.Null(MyInt64); + Assert.Null(MyUInt16); + Assert.Null(MyUInt32); + Assert.Null(MyUInt64); + Assert.Null(MyByte); + Assert.Null(MySByte); + Assert.Null(MyChar); + Assert.Null(MyDecimal); + Assert.Null(MyBooleanFalse); + Assert.Null(MyBooleanTrue); + Assert.Null(MySingle); + Assert.Null(MyDouble); + Assert.Null(MyDateTime); + Assert.Null(MyDateTimeOffset); + Assert.Null(MyEnum); + + Assert.Null(MyInt16Array); + Assert.Null(MyInt32Array); + Assert.Null(MyInt64Array); + Assert.Null(MyUInt16Array); + Assert.Null(MyUInt32Array); + Assert.Null(MyUInt64Array); + Assert.Null(MyByteArray); + Assert.Null(MySByteArray); + Assert.Null(MyCharArray); + Assert.Null(MyDecimalArray); + Assert.Null(MyBooleanFalseArray); + Assert.Null(MyBooleanTrueArray); + Assert.Null(MySingleArray); + Assert.Null(MyDoubleArray); + Assert.Null(MyDateTimeArray); + Assert.Null(MyDateTimeOffsetArray); + Assert.Null(MyEnumArray); + } + public static readonly string s_json = + @"{" + + @"""MyInt16"" : null," + + @"""MyInt32"" : null," + + @"""MyInt64"" : null," + + @"""MyUInt16"" : null," + + @"""MyUInt32"" : null," + + @"""MyUInt64"" : null," + + @"""MyByte"" : null," + + @"""MySByte"" : null," + + @"""MyChar"" : null," + + @"""MyBooleanTrue"" : null," + + @"""MyBooleanFalse"" : null," + + @"""MySingle"" : null," + + @"""MyDouble"" : null," + + @"""MyDecimal"" : null," + + @"""MyDateTime"" : null," + + @"""MyDateTimeOffset"" : null," + + @"""MyEnum"" : null," + + @"""MyInt16Array"" : null," + + @"""MyInt32Array"" : null," + + @"""MyInt64Array"" : null," + + @"""MyUInt16Array"" : null," + + @"""MyUInt32Array"" : null," + + @"""MyUInt64Array"" : null," + + @"""MyByteArray"" : null," + + @"""MySByteArray"" : null," + + @"""MyCharArray"" : null," + + @"""MyBooleanTrueArray"" : null," + + @"""MyBooleanFalseArray"" : null," + + @"""MySingleArray"" : null," + + @"""MyDoubleArray"" : null," + + @"""MyDecimalArray"" : null," + + @"""MyDateTimeArray"" : null," + + @"""MyDateTimeOffsetArray"" : null," + + @"""MyEnumArray"" : null" + + @"}"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + } + + public class SimpleTestClassWithNullables : SimpleBaseClassWithNullables, ITestClass + { + public static readonly string s_json = + @"{" + + @"""MyInt16"" : 1," + + @"""MyInt32"" : 2," + + @"""MyInt64"" : 3," + + @"""MyUInt16"" : 4," + + @"""MyUInt32"" : 5," + + @"""MyUInt64"" : 6," + + @"""MyByte"" : 7," + + @"""MySByte"" : 8," + + @"""MyChar"" : ""a""," + + @"""MyBooleanTrue"" : true," + + @"""MyBooleanFalse"" : false," + + @"""MySingle"" : 1.1," + + @"""MyDouble"" : 2.2," + + @"""MyDecimal"" : 3.3," + + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + + @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + + @"""MyEnum"" : 2," + + @"""MyInt16Array"" : [1]," + + @"""MyInt32Array"" : [2]," + + @"""MyInt64Array"" : [3]," + + @"""MyUInt16Array"" : [4]," + + @"""MyUInt32Array"" : [5]," + + @"""MyUInt64Array"" : [6]," + + @"""MyByteArray"" : [7]," + + @"""MySByteArray"" : [8]," + + @"""MyCharArray"" : [""a""]," + + @"""MyBooleanTrueArray"" : [true]," + + @"""MyBooleanFalseArray"" : [false]," + + @"""MySingleArray"" : [1.1]," + + @"""MyDoubleArray"" : [2.2]," + + @"""MyDecimalArray"" : [3.3]," + + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + + @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + + @"""MyEnumArray"" : [2]" + + @"}"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + public void Initialize() + { + MyInt16 = 1; + MyInt32 = 2; + MyInt64 = 3; + MyUInt16 = 4; + MyUInt32 = 5; + MyUInt64 = 6; + MyByte = 7; + MySByte = 8; + MyChar = 'a'; + MyBooleanTrue = true; + MyBooleanFalse = false; + MySingle = 1.1f; + MyDouble = 2.2d; + MyDecimal = 3.3m; + MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); + MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); + MyEnum = SampleEnum.Two; + + MyInt16Array = new short?[] { 1 }; + MyInt32Array = new int?[] { 2 }; + MyInt64Array = new long?[] { 3 }; + MyUInt16Array = new ushort?[] { 4 }; + MyUInt32Array = new uint?[] { 5 }; + MyUInt64Array = new ulong?[] { 6 }; + MyByteArray = new byte?[] { 7 }; + MySByteArray = new sbyte?[] { 8 }; + MyCharArray = new char?[] { 'a' }; + MyBooleanTrueArray = new bool?[] { true }; + MyBooleanFalseArray = new bool?[] { false }; + MySingleArray = new float?[] { 1.1f }; + MyDoubleArray = new double?[] { 2.2d }; + MyDecimalArray = new decimal?[] { 3.3m }; + MyDateTimeArray = new DateTime?[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; + MyDateTimeOffsetArray = new DateTimeOffset?[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; + MyEnumArray = new SampleEnum?[] { SampleEnum.Two }; + } + + public void Verify() + { + Assert.Equal(MyInt16, (short)1); + Assert.Equal(MyInt32, (int)2); + Assert.Equal(MyInt64, (long)3); + Assert.Equal(MyUInt16, (ushort)4); + Assert.Equal(MyUInt32, (uint)5); + Assert.Equal(MyUInt64, (ulong)6); + Assert.Equal(MyByte, (byte)7); + Assert.Equal(MySByte, (sbyte)8); + Assert.Equal(MyChar, 'a'); + Assert.Equal(MyDecimal, 3.3m); + Assert.Equal(MyBooleanFalse, false); + Assert.Equal(MyBooleanTrue, true); + Assert.Equal(MySingle, 1.1f); + Assert.Equal(MyDouble, 2.2d); + Assert.Equal(MyDateTime, new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc)); + Assert.Equal(MyDateTimeOffset, new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0))); + Assert.Equal(MyEnum, SampleEnum.Two); + + Assert.Equal((short)1, MyInt16Array[0]); + Assert.Equal((int)2, MyInt32Array[0]); + Assert.Equal((long)3, MyInt64Array[0]); + Assert.Equal((ushort)4, MyUInt16Array[0]); + Assert.Equal((uint)5, MyUInt32Array[0]); + Assert.Equal((ulong)6, MyUInt64Array[0]); + Assert.Equal((byte)7, MyByteArray[0]); + Assert.Equal((sbyte)8, MySByteArray[0]); + Assert.Equal('a', MyCharArray[0]); + Assert.Equal(3.3m, MyDecimalArray[0]); + Assert.Equal(false, MyBooleanFalseArray[0]); + Assert.Equal(true, MyBooleanTrueArray[0]); + Assert.Equal(1.1f, MySingleArray[0]); + Assert.Equal(2.2d, MyDoubleArray[0]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); + Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); + Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + } + } +} diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs new file mode 100644 index 000000000000..f7265932aaa0 --- /dev/null +++ b/src/System.Text.Json/tests/Serialization/TestClasses.SimpleTestClassWithObject.cs @@ -0,0 +1,192 @@ +// 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.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Text.Json.Serialization.Tests +{ + public class SimpleTestClassWithObject : ITestClass + { + public object MyInt16 { get; set; } + public object MyInt32 { get; set; } + public object MyInt64 { get; set; } + public object MyUInt16 { get; set; } + public object MyUInt32 { get; set; } + public object MyUInt64 { get; set; } + public object MyByte { get; set; } + public object MySByte { get; set; } + public object MyChar { get; set; } + public object MyString { get; set; } + public object MyDecimal { get; set; } + public object MyBooleanTrue { get; set; } + public object MyBooleanFalse { get; set; } + public object MySingle { get; set; } + public object MyDouble { get; set; } + public object MyDateTime { get; set; } + public object MyEnum { get; set; } + public object MyInt16Array { get; set; } + public object MyInt32Array { get; set; } + public object MyInt64Array { get; set; } + public object MyUInt16Array { get; set; } + public object MyUInt32Array { get; set; } + public object MyUInt64Array { get; set; } + public object MyByteArray { get; set; } + public object MySByteArray { get; set; } + public object MyCharArray { get; set; } + public object MyStringArray { get; set; } + public object MyDecimalArray { get; set; } + public object MyBooleanTrueArray { get; set; } + public object MyBooleanFalseArray { get; set; } + public object MySingleArray { get; set; } + public object MyDoubleArray { get; set; } + public object MyDateTimeArray { get; set; } + public object MyEnumArray { get; set; } + public object MyStringList { get; set; } + public object MyStringIEnumerableT { get; set; } + public object MyStringIListT { get; set; } + public object MyStringICollectionT { get; set; } + public object MyStringIReadOnlyCollectionT { get; set; } + public object MyStringIReadOnlyListT { get; set; } + + public static readonly string s_json = + @"{" + + @"""MyInt16"" : 1," + + @"""MyInt32"" : 2," + + @"""MyInt64"" : 3," + + @"""MyUInt16"" : 4," + + @"""MyUInt32"" : 5," + + @"""MyUInt64"" : 6," + + @"""MyByte"" : 7," + + @"""MySByte"" : 8," + + @"""MyChar"" : ""a""," + + @"""MyString"" : ""Hello""," + + @"""MyBooleanTrue"" : true," + + @"""MyBooleanFalse"" : false," + + @"""MySingle"" : 1.1," + + @"""MyDouble"" : 2.2," + + @"""MyDecimal"" : 3.3," + + @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + + @"""MyEnum"" : 2," + // int by default + @"""MyInt16Array"" : [1]," + + @"""MyInt32Array"" : [2]," + + @"""MyInt64Array"" : [3]," + + @"""MyUInt16Array"" : [4]," + + @"""MyUInt32Array"" : [5]," + + @"""MyUInt64Array"" : [6]," + + @"""MyByteArray"" : [7]," + + @"""MySByteArray"" : [8]," + + @"""MyCharArray"" : [""a""]," + + @"""MyStringArray"" : [""Hello""]," + + @"""MyBooleanTrueArray"" : [true]," + + @"""MyBooleanFalseArray"" : [false]," + + @"""MySingleArray"" : [1.1]," + + @"""MyDoubleArray"" : [2.2]," + + @"""MyDecimalArray"" : [3.3]," + + @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + + @"""MyEnumArray"" : [2]," + // int by default + @"""MyStringList"" : [""Hello""]," + + @"""MyStringIEnumerableT"" : [""Hello""]," + + @"""MyStringIListT"" : [""Hello""]," + + @"""MyStringICollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyCollectionT"" : [""Hello""]," + + @"""MyStringIReadOnlyListT"" : [""Hello""]" + + @"}"; + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + + public void Initialize() + { + MyInt16 = (short)1; + MyInt32 = (int)2; + MyInt64 = (long)3; + MyUInt16 = (ushort)4; + MyUInt32 = (uint)5; + MyUInt64 = (ulong)6; + MyByte = (byte)7; + MySByte = (sbyte)8; + MyChar = 'a'; + MyString = "Hello"; + MyBooleanTrue = true; + MyBooleanFalse = false; + MySingle = 1.1f; + MyDouble = 2.2d; + MyDecimal = 3.3m; + MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); + MyEnum = SampleEnum.Two; + + MyInt16Array = new short[] { 1 }; + MyInt32Array = new int[] { 2 }; + MyInt64Array = new long[] { 3 }; + MyUInt16Array = new ushort[] { 4 }; + MyUInt32Array = new uint[] { 5 }; + MyUInt64Array = new ulong[] { 6 }; + MyByteArray = new byte[] { 7 }; + MySByteArray = new sbyte[] { 8 }; + MyCharArray = new char[] { 'a' }; + MyStringArray = new string[] { "Hello" }; + MyBooleanTrueArray = new bool[] { true }; + MyBooleanFalseArray = new bool[] { false }; + MySingleArray = new float[] { 1.1f }; + MyDoubleArray = new double[] { 2.2d }; + MyDecimalArray = new decimal[] { 3.3m }; + MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; + MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + + MyStringList = new List() { "Hello" }; + MyStringIEnumerableT = new string[] { "Hello" }; + MyStringIListT = new string[] { "Hello" }; + MyStringICollectionT = new string[] { "Hello" }; + MyStringIReadOnlyCollectionT = new string[] { "Hello" }; + MyStringIReadOnlyListT = new string[] { "Hello" }; + } + + public void Verify() + { + Assert.Equal((short)1, MyInt16); + Assert.Equal((int)2, MyInt32); + Assert.Equal((long)3, MyInt64); + Assert.Equal((ushort)4, MyUInt16); + Assert.Equal((uint)5, MyUInt32); + Assert.Equal((ulong)6, MyUInt64); + Assert.Equal((byte)7, MyByte); + Assert.Equal((sbyte)8, MySByte); + Assert.Equal('a', MyChar); + Assert.Equal("Hello", MyString); + Assert.Equal(3.3m, MyDecimal); + Assert.Equal(false, MyBooleanFalse); + Assert.Equal(true, MyBooleanTrue); + Assert.Equal(1.1f, MySingle); + Assert.Equal(2.2d, MyDouble); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); + Assert.Equal(SampleEnum.Two, MyEnum); + + Assert.Equal((short)1, ((short[])MyInt16Array)[0]); + Assert.Equal((int)2, ((int[])MyInt32Array)[0]); + Assert.Equal((long)3, ((long[])MyInt64Array)[0]); + Assert.Equal((ushort)4, ((ushort[])MyUInt16Array)[0]); + Assert.Equal((uint)5, ((uint[])MyUInt32Array)[0]); + Assert.Equal((ulong)6, ((ulong[])MyUInt64Array)[0]); + Assert.Equal((byte)7, ((byte[])MyByteArray)[0]); + Assert.Equal((sbyte)8, ((sbyte[])MySByteArray)[0]); + Assert.Equal('a', ((char[])MyCharArray)[0]); + Assert.Equal("Hello", ((string[])MyStringArray)[0]); + Assert.Equal(3.3m, ((decimal[])MyDecimalArray)[0]); + Assert.Equal(false, ((bool[])MyBooleanFalseArray)[0]); + Assert.Equal(true, ((bool[])MyBooleanTrueArray)[0]); + Assert.Equal(1.1f, ((float[])MySingleArray)[0]); + Assert.Equal(2.2d, ((double[])MyDoubleArray)[0]); + Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), ((DateTime[])MyDateTimeArray)[0]); + Assert.Equal(SampleEnum.Two, ((SampleEnum[])MyEnumArray)[0]); + + Assert.Equal("Hello", ((List)MyStringList)[0]); + Assert.Equal("Hello", ((IEnumerable)MyStringIEnumerableT).First()); + Assert.Equal("Hello", ((IList)MyStringIListT)[0]); + Assert.Equal("Hello", ((ICollection)MyStringICollectionT).First()); + Assert.Equal("Hello", ((IReadOnlyCollection)MyStringIReadOnlyCollectionT).First()); + Assert.Equal("Hello", ((IReadOnlyList)MyStringIReadOnlyListT)[0]); + } + } +} diff --git a/src/System.Text.Json/tests/Serialization/TestClasses.cs b/src/System.Text.Json/tests/Serialization/TestClasses.cs index 0d4761579ac6..f6c7195ffe9a 100644 --- a/src/System.Text.Json/tests/Serialization/TestClasses.cs +++ b/src/System.Text.Json/tests/Serialization/TestClasses.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Linq; using Xunit; namespace System.Text.Json.Serialization.Tests @@ -19,548 +20,231 @@ public enum SampleEnum Two = 2 } - public class SimpleTestClass : ITestClass + public class TestClassWithNull { - public short MyInt16 { get; set; } - public int MyInt32 { get; set; } - public long MyInt64 { get; set; } - public ushort MyUInt16 { get; set; } - public uint MyUInt32 { get; set; } - public ulong MyUInt64 { get; set; } - public byte MyByte { get; set; } - public sbyte MySByte { get; set; } - public char MyChar { get; set; } public string MyString { get; set; } - public decimal MyDecimal { get; set; } - public bool MyBooleanTrue { get; set; } - public bool MyBooleanFalse { get; set; } - public float MySingle { get; set; } - public double MyDouble { get; set; } - public DateTime MyDateTime { get; set; } - public DateTimeOffset MyDateTimeOffset { get; set; } - public SampleEnum MyEnum { get; set; } - public short[] MyInt16Array { get; set; } - public int[] MyInt32Array { get; set; } - public long[] MyInt64Array { get; set; } - public ushort[] MyUInt16Array { get; set; } - public uint[] MyUInt32Array { get; set; } - public ulong[] MyUInt64Array { get; set; } - public byte[] MyByteArray { get; set; } - public sbyte[] MySByteArray { get; set; } - public char[] MyCharArray { get; set; } - public string[] MyStringArray { get; set; } - public decimal[] MyDecimalArray { get; set; } - public bool[] MyBooleanTrueArray { get; set; } - public bool[] MyBooleanFalseArray { get; set; } - public float[] MySingleArray { get; set; } - public double[] MyDoubleArray { get; set; } - public DateTime[] MyDateTimeArray { get; set; } - public DateTimeOffset[] MyDateTimeOffsetArray { get; set; } - public SampleEnum[] MyEnumArray { get; set; } - public static readonly string s_json = @"{" + - @"""MyInt16"" : 1," + - @"""MyInt32"" : 2," + - @"""MyInt64"" : 3," + - @"""MyUInt16"" : 4," + - @"""MyUInt32"" : 5," + - @"""MyUInt64"" : 6," + - @"""MyByte"" : 7," + - @"""MySByte"" : 8," + - @"""MyChar"" : ""a""," + - @"""MyString"" : ""Hello""," + - @"""MyBooleanTrue"" : true," + - @"""MyBooleanFalse"" : false," + - @"""MySingle"" : 1.1," + - @"""MyDouble"" : 2.2," + - @"""MyDecimal"" : 3.3," + - @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + - @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + - @"""MyEnum"" : 2," + // int by default - @"""MyInt16Array"" : [1]," + - @"""MyInt32Array"" : [2]," + - @"""MyInt64Array"" : [3]," + - @"""MyUInt16Array"" : [4]," + - @"""MyUInt32Array"" : [5]," + - @"""MyUInt64Array"" : [6]," + - @"""MyByteArray"" : [7]," + - @"""MySByteArray"" : [8]," + - @"""MyCharArray"" : [""a""]," + - @"""MyStringArray"" : [""Hello""]," + - @"""MyBooleanTrueArray"" : [true]," + - @"""MyBooleanFalseArray"" : [false]," + - @"""MySingleArray"" : [1.1]," + - @"""MyDoubleArray"" : [2.2]," + - @"""MyDecimalArray"" : [3.3]," + - @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + - @"""MyDateTimeOffsetArray"" : [""2019-01-30T12:01:02.0000000+01:00""]," + - @"""MyEnumArray"" : [2]" + // int by default + @"""MyString"" : null" + @"}"; public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); public void Initialize() { - MyInt16 = 1; - MyInt32 = 2; - MyInt64 = 3; - MyUInt16 = 4; - MyUInt32 = 5; - MyUInt64 = 6; - MyByte = 7; - MySByte = 8; - MyChar = 'a'; - MyString = "Hello"; - MyBooleanTrue = true; - MyBooleanFalse = false; - MySingle = 1.1f; - MyDouble = 2.2d; - MyDecimal = 3.3m; - MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); - MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); - MyEnum = SampleEnum.Two; - - MyInt16Array = new short[] { 1 }; - MyInt32Array = new int[] { 2 }; - MyInt64Array = new long[] { 3 }; - MyUInt16Array = new ushort[] { 4 }; - MyUInt32Array = new uint[] { 5 }; - MyUInt64Array = new ulong[] { 6 }; - MyByteArray = new byte[] { 7 }; - MySByteArray = new sbyte[] { 8 }; - MyCharArray = new char[] { 'a' }; - MyStringArray = new string[] { "Hello" }; - MyBooleanTrueArray = new bool[] { true }; - MyBooleanFalseArray = new bool[] { false }; - MySingleArray = new float[] { 1.1f }; - MyDoubleArray = new double[] { 2.2d }; - MyDecimalArray = new decimal[] { 3.3m }; - MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; - MyDateTimeOffsetArray = new DateTimeOffset[] { new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)) }; - MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + MyString = null; } public void Verify() { - Assert.Equal((short)1, MyInt16); - Assert.Equal((int)2, MyInt32); - Assert.Equal((long)3, MyInt64); - Assert.Equal((ushort)4, MyUInt16); - Assert.Equal((uint)5, MyUInt32); - Assert.Equal((ulong)6, MyUInt64); - Assert.Equal((byte)7, MyByte); - Assert.Equal((sbyte)8, MySByte); - Assert.Equal('a', MyChar); - Assert.Equal("Hello", MyString); - Assert.Equal(3.3m, MyDecimal); - Assert.Equal(false, MyBooleanFalse); - Assert.Equal(true, MyBooleanTrue); - Assert.Equal(1.1f, MySingle); - Assert.Equal(2.2d, MyDouble); - Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); - Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffset); - Assert.Equal(SampleEnum.Two, MyEnum); - - Assert.Equal((short)1, MyInt16Array[0]); - Assert.Equal((int)2, MyInt32Array[0]); - Assert.Equal((long)3, MyInt64Array[0]); - Assert.Equal((ushort)4, MyUInt16Array[0]); - Assert.Equal((uint)5, MyUInt32Array[0]); - Assert.Equal((ulong)6, MyUInt64Array[0]); - Assert.Equal((byte)7, MyByteArray[0]); - Assert.Equal((sbyte)8, MySByteArray[0]); - Assert.Equal('a', MyCharArray[0]); - Assert.Equal("Hello", MyStringArray[0]); - Assert.Equal(3.3m, MyDecimalArray[0]); - Assert.Equal(false, MyBooleanFalseArray[0]); - Assert.Equal(true, MyBooleanTrueArray[0]); - Assert.Equal(1.1f, MySingleArray[0]); - Assert.Equal(2.2d, MyDoubleArray[0]); - Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTimeArray[0]); - Assert.Equal(new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)), MyDateTimeOffsetArray[0]); - Assert.Equal(SampleEnum.Two, MyEnumArray[0]); + Assert.Equal(MyString, null); } } - public class SimpleTestClassWithObject : ITestClass + public class TestClassWithInitializedProperties { - public object MyInt16 { get; set; } - public object MyInt32 { get; set; } - public object MyInt64 { get; set; } - public object MyUInt16 { get; set; } - public object MyUInt32 { get; set; } - public object MyUInt64 { get; set; } - public object MyByte { get; set; } - public object MySByte { get; set; } - public object MyChar { get; set; } - public object MyString { get; set; } - public object MyDecimal { get; set; } - public object MyBooleanTrue { get; set; } - public object MyBooleanFalse { get; set; } - public object MySingle { get; set; } - public object MyDouble { get; set; } - public object MyDateTime { get; set; } - public object MyEnum { get; set; } - public object MyInt16Array { get; set; } - public object MyInt32Array { get; set; } - public object MyInt64Array { get; set; } - public object MyUInt16Array { get; set; } - public object MyUInt32Array { get; set; } - public object MyUInt64Array { get; set; } - public object MyByteArray { get; set; } - public object MySByteArray { get; set; } - public object MyCharArray { get; set; } - public object MyStringArray { get; set; } - public object MyDecimalArray { get; set; } - public object MyBooleanTrueArray { get; set; } - public object MyBooleanFalseArray { get; set; } - public object MySingleArray { get; set; } - public object MyDoubleArray { get; set; } - public object MyDateTimeArray { get; set; } - public object MyEnumArray { get; set; } - - public static readonly string s_json = + public string MyString { get; set; } = "Hello"; + public int? MyInt { get; set; } = 1; + public static readonly string s_null_json = @"{" + - @"""MyInt16"" : 1," + - @"""MyInt32"" : 2," + - @"""MyInt64"" : 3," + - @"""MyUInt16"" : 4," + - @"""MyUInt32"" : 5," + - @"""MyUInt64"" : 6," + - @"""MyByte"" : 7," + - @"""MySByte"" : 8," + - @"""MyChar"" : ""a""," + - @"""MyString"" : ""Hello""," + - @"""MyBooleanTrue"" : true," + - @"""MyBooleanFalse"" : false," + - @"""MySingle"" : 1.1," + - @"""MyDouble"" : 2.2," + - @"""MyDecimal"" : 3.3," + - @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + - @"""MyEnum"" : 2," + // int by default - @"""MyInt16Array"" : [1]," + - @"""MyInt32Array"" : [2]," + - @"""MyInt64Array"" : [3]," + - @"""MyUInt16Array"" : [4]," + - @"""MyUInt32Array"" : [5]," + - @"""MyUInt64Array"" : [6]," + - @"""MyByteArray"" : [7]," + - @"""MySByteArray"" : [8]," + - @"""MyCharArray"" : [""a""]," + - @"""MyStringArray"" : [""Hello""]," + - @"""MyBooleanTrueArray"" : [true]," + - @"""MyBooleanFalseArray"" : [false]," + - @"""MySingleArray"" : [1.1]," + - @"""MyDoubleArray"" : [2.2]," + - @"""MyDecimalArray"" : [3.3]," + - @"""MyDateTimeArray"" : [""2019-01-30T12:01:02.0000000Z""]," + - @"""MyEnumArray"" : [2]" + // int by default + @"""MyString"" : null," + + @"""MyInt"" : null" + @"}"; + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_null_json); + } + + public class TestClassWithNestedObjectInner : ITestClass + { + public SimpleTestClass MyData { get; set; } + + public static readonly string s_json = + @"{" + + @"""MyData"":" + SimpleTestClass.s_json + + @"}"; + public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); public void Initialize() { - MyInt16 = (short)1; - MyInt32 = (int)2; - MyInt64 = (long)3; - MyUInt16 = (ushort)4; - MyUInt32 = (uint)5; - MyUInt64 = (ulong)6; - MyByte = (byte)7; - MySByte =(sbyte) 8; - MyChar = 'a'; - MyString = "Hello"; - MyBooleanTrue = true; - MyBooleanFalse = false; - MySingle = 1.1f; - MyDouble = 2.2d; - MyDecimal = 3.3m; - MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); - MyEnum = SampleEnum.Two; - - MyInt16Array = new short[] { 1 }; - MyInt32Array = new int[] { 2 }; - MyInt64Array = new long[] { 3 }; - MyUInt16Array = new ushort[] { 4 }; - MyUInt32Array = new uint[] { 5 }; - MyUInt64Array = new ulong[] { 6 }; - MyByteArray = new byte[] { 7 }; - MySByteArray = new sbyte[] { 8 }; - MyCharArray = new char[] { 'a' }; - MyStringArray = new string[] { "Hello" }; - MyBooleanTrueArray = new bool[] { true }; - MyBooleanFalseArray = new bool[] { false }; - MySingleArray = new float[] { 1.1f }; - MyDoubleArray = new double[] { 2.2d }; - MyDecimalArray = new decimal[] { 3.3m }; - MyDateTimeArray = new DateTime[] { new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc) }; - MyEnumArray = new SampleEnum[] { SampleEnum.Two }; + MyData = new SimpleTestClass(); + MyData.Initialize(); } public void Verify() { - Assert.Equal((short)1, MyInt16); - Assert.Equal((int)2, MyInt32); - Assert.Equal((long)3, MyInt64); - Assert.Equal((ushort)4, MyUInt16); - Assert.Equal((uint)5, MyUInt32); - Assert.Equal((ulong)6, MyUInt64); - Assert.Equal((byte)7, MyByte); - Assert.Equal((sbyte)8, MySByte); - Assert.Equal('a', MyChar); - Assert.Equal("Hello", MyString); - Assert.Equal(3.3m, MyDecimal); - Assert.Equal(false, MyBooleanFalse); - Assert.Equal(true, MyBooleanTrue); - Assert.Equal(1.1f, MySingle); - Assert.Equal(2.2d, MyDouble); - Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), MyDateTime); - Assert.Equal(SampleEnum.Two, MyEnum); - - Assert.Equal((short)1, ((short[])MyInt16Array)[0]); - Assert.Equal((int)2, ((int[])MyInt32Array)[0]); - Assert.Equal((long)3, ((long[])MyInt64Array)[0]); - Assert.Equal((ushort)4, ((ushort[])MyUInt16Array)[0]); - Assert.Equal((uint)5, ((uint[])MyUInt32Array)[0]); - Assert.Equal((ulong)6, ((ulong[])MyUInt64Array)[0]); - Assert.Equal((byte)7, ((byte[])MyByteArray)[0]); - Assert.Equal((sbyte)8, ((sbyte[])MySByteArray)[0]); - Assert.Equal('a', ((char[])MyCharArray)[0]); - Assert.Equal("Hello", ((string[])MyStringArray)[0]); - Assert.Equal(3.3m, ((decimal[])MyDecimalArray)[0]); - Assert.Equal(false, ((bool[])MyBooleanFalseArray)[0]); - Assert.Equal(true, ((bool[])MyBooleanTrueArray)[0]); - Assert.Equal(1.1f, ((float[])MySingleArray)[0]); - Assert.Equal(2.2d, ((double[])MyDoubleArray)[0]); - Assert.Equal(new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc), ((DateTime[])MyDateTimeArray)[0]); - Assert.Equal(SampleEnum.Two, ((SampleEnum[])MyEnumArray)[0]); + Assert.NotNull(MyData); + MyData.Verify(); } } - public abstract class SimpleBaseClassWithNullables + public class TestClassWithNestedObjectOuter : ITestClass { - public short? MyInt16 { get; set; } - public int? MyInt32 { get; set; } - public long? MyInt64 { get; set; } - public ushort? MyUInt16 { get; set; } - public uint? MyUInt32 { get; set; } - public ulong? MyUInt64 { get; set; } - public byte? MyByte { get; set; } - public sbyte? MySByte { get; set; } - public char? MyChar { get; set; } - public decimal? MyDecimal { get; set; } - public bool? MyBooleanTrue { get; set; } - public bool? MyBooleanFalse { get; set; } - public float? MySingle { get; set; } - public double? MyDouble { get; set; } - public DateTime? MyDateTime { get; set; } - public DateTimeOffset? MyDateTimeOffset { get; set; } - public SampleEnum? MyEnum { get; set; } - } + public TestClassWithNestedObjectInner MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":" + TestClassWithNestedObjectInner.s_json + + @"}"); - public class SimpleTestClassWithNulls : SimpleBaseClassWithNullables, ITestClass - { public void Initialize() { + MyData = new TestClassWithNestedObjectInner(); + MyData.Initialize(); } public void Verify() { - Assert.Null(MyInt16); - Assert.Null(MyInt32); - Assert.Null(MyInt64); - Assert.Null(MyUInt16); - Assert.Null(MyUInt32); - Assert.Null(MyUInt64); - Assert.Null(MyByte); - Assert.Null(MySByte); - Assert.Null(MyChar); - Assert.Null(MyDecimal); - Assert.Null(MyBooleanFalse); - Assert.Null(MyBooleanTrue); - Assert.Null(MySingle); - Assert.Null(MyDouble); - Assert.Null(MyDateTime); - Assert.Null(MyDateTimeOffset); - Assert.Null(MyEnum); + Assert.NotNull(MyData); + MyData.Verify(); } - public static readonly string s_json = - @"{" + - @"""MyInt16"" : null," + - @"""MyInt32"" : null," + - @"""MyInt64"" : null," + - @"""MyUInt16"" : null," + - @"""MyUInt32"" : null," + - @"""MyUInt64"" : null," + - @"""MyByte"" : null," + - @"""MySByte"" : null," + - @"""MyChar"" : null," + - @"""MyBooleanTrue"" : null," + - @"""MyBooleanFalse"" : null," + - @"""MySingle"" : null," + - @"""MyDouble"" : null," + - @"""MyDecimal"" : null," + - @"""MyDateTime"" : null," + - @"""MyDateTimeOffset"" : null," + - @"""MyEnum"" : null" + - @"}"; - - public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); } - public class SimpleTestClassWithNullables : SimpleBaseClassWithNullables, ITestClass + public class TestClassWithObjectList : ITestClass { - public static readonly string s_json = - @"{" + - @"""MyInt16"" : 1," + - @"""MyInt32"" : 2," + - @"""MyInt64"" : 3," + - @"""MyUInt16"" : 4," + - @"""MyUInt32"" : 5," + - @"""MyUInt64"" : 6," + - @"""MyByte"" : 7," + - @"""MySByte"" : 8," + - @"""MyChar"" : ""a""," + - @"""MyBooleanTrue"" : true," + - @"""MyBooleanFalse"" : false," + - @"""MySingle"" : 1.1," + - @"""MyDouble"" : 2.2," + - @"""MyDecimal"" : 3.3," + - @"""MyDateTime"" : ""2019-01-30T12:01:02.0000000Z""," + - @"""MyDateTimeOffset"" : ""2019-01-30T12:01:02.0000000+01:00""," + - @"""MyEnum"" : 2" + // int by default - @"}"; + public List MyData { get; set; } - public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); public void Initialize() { - MyInt16 = 1; - MyInt32 = 2; - MyInt64 = 3; - MyUInt16 = 4; - MyUInt32 = 5; - MyUInt64 = 6; - MyByte = 7; - MySByte = 8; - MyChar = 'a'; - MyBooleanTrue = true; - MyBooleanFalse = false; - MySingle = 1.1f; - MyDouble = 2.2d; - MyDecimal = 3.3m; - MyDateTime = new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc); - MyDateTimeOffset = new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0)); - MyEnum = SampleEnum.Two; + MyData = new List(); + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } } public void Verify() { - Assert.Equal(MyInt16, (short)1); - Assert.Equal(MyInt32, (int)2); - Assert.Equal(MyInt64, (long)3); - Assert.Equal(MyUInt16, (ushort)4); - Assert.Equal(MyUInt32, (uint)5); - Assert.Equal(MyUInt64, (ulong)6); - Assert.Equal(MyByte, (byte)7); - Assert.Equal(MySByte, (sbyte)8); - Assert.Equal(MyChar, 'a'); - Assert.Equal(MyDecimal, 3.3m); - Assert.Equal(MyBooleanFalse, false); - Assert.Equal(MyBooleanTrue, true); - Assert.Equal(MySingle, 1.1f); - Assert.Equal(MyDouble, 2.2d); - Assert.Equal(MyDateTime, new DateTime(2019, 1, 30, 12, 1, 2, DateTimeKind.Utc)); - Assert.Equal(MyDateTimeOffset, new DateTimeOffset(2019, 1, 30, 12, 1, 2, new TimeSpan(1, 0, 0))); - Assert.Equal(MyEnum, SampleEnum.Two); + Assert.Equal(2, MyData.Count); + MyData[0].Verify(); + MyData[1].Verify(); } } - public class TestClassWithNull + public class TestClassWithObjectArray : ITestClass { - public string MyString { get; set; } - public static readonly string s_json = - @"{" + - @"""MyString"" : null" + - @"}"; + public SimpleTestClass[] MyData { get; set; } - public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); public void Initialize() { - MyString = null; + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new SimpleTestClass[2] { obj1, obj2 }; } public void Verify() { - Assert.Equal(MyString, null); + MyData[0].Verify(); + MyData[1].Verify(); + Assert.Equal(2, MyData.Length); } } - public class TestClassWithNullButInitialized + public class TestClassWithObjectIEnumerableT : ITestClass { - public string MyString { get; set; } = "Hello"; - public int? MyInt { get; set; } = 1; - public static readonly string s_json = - @"{" + - @"""MyString"" : null," + - @"""MyInt"" : null" + - @"}"; - - public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); - } + public IEnumerable MyData { get; set; } - public class TestClassWithNestedObjectInner : ITestClass - { - public SimpleTestClass MyData { get; set; } - - public static readonly string s_json = + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( @"{" + - @"""MyData"":" + SimpleTestClass.s_json + - @"}"; - - public static readonly byte[] s_data = Encoding.UTF8.GetBytes(s_json); + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); public void Initialize() { - MyData = new SimpleTestClass(); - MyData.Initialize(); + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new SimpleTestClass[] { obj1, obj2 }; } public void Verify() { - Assert.NotNull(MyData); - MyData.Verify(); + int count = 0; + + foreach (SimpleTestClass data in MyData) + { + data.Verify(); + count++; + } + + Assert.Equal(2, count); } } - public class TestClassWithNestedObjectOuter : ITestClass + public class TestClassWithObjectIListT : ITestClass { - public TestClassWithNestedObjectInner MyData { get; set; } + public IList MyData { get; set; } public static readonly byte[] s_data = Encoding.UTF8.GetBytes( @"{" + - @"""MyData"":" + TestClassWithNestedObjectInner.s_json + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + @"}"); public void Initialize() { - MyData = new TestClassWithNestedObjectInner(); - MyData.Initialize(); + MyData = new List(); + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } + + { + SimpleTestClass obj = new SimpleTestClass(); + obj.Initialize(); + MyData.Add(obj); + } } public void Verify() { - Assert.NotNull(MyData); - MyData.Verify(); + Assert.Equal(2, MyData.Count); + MyData[0].Verify(); + MyData[1].Verify(); } } - public class TestClassWithObjectList : ITestClass + public class TestClassWithObjectICollectionT : ITestClass { - public List MyData { get; set; } + public ICollection MyData { get; set; } public static readonly byte[] s_data = Encoding.UTF8.GetBytes( @"{" + @@ -590,14 +274,17 @@ public void Initialize() public void Verify() { Assert.Equal(2, MyData.Count); - MyData[0].Verify(); - MyData[1].Verify(); + + foreach (SimpleTestClass data in MyData) + { + data.Verify(); + } } } - public class TestClassWithObjectArray : ITestClass + public class TestClassWithObjectIReadOnlyCollectionT : ITestClass { - public SimpleTestClass[] MyData { get; set; } + public IReadOnlyCollection MyData { get; set; } public static readonly byte[] s_data = Encoding.UTF8.GetBytes( @"{" + @@ -615,14 +302,48 @@ public void Initialize() SimpleTestClass obj2 = new SimpleTestClass(); obj2.Initialize(); - MyData = new SimpleTestClass[2] { obj1, obj2 }; + MyData = new SimpleTestClass[] { obj1, obj2 }; } public void Verify() { + Assert.Equal(2, MyData.Count); + + foreach (SimpleTestClass data in MyData) + { + data.Verify(); + } + } + } + + public class TestClassWithObjectIReadOnlyListT : ITestClass + { + public IReadOnlyList MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + SimpleTestClass.s_json + "," + + SimpleTestClass.s_json + + @"]" + + @"}"); + + public void Initialize() + { + SimpleTestClass obj1 = new SimpleTestClass(); + obj1.Initialize(); + + SimpleTestClass obj2 = new SimpleTestClass(); + obj2.Initialize(); + + MyData = new SimpleTestClass[] { obj1, obj2 }; + } + + public void Verify() + { + Assert.Equal(2, MyData.Count); MyData[0].Verify(); MyData[1].Verify(); - Assert.Equal(2, MyData.Length); } } @@ -691,6 +412,181 @@ public void Verify() } } + public class TestClassWithGenericIEnumerableT : ITestClass + { + public IEnumerable MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + + int count = 0; + foreach (string data in MyData) + { + count++; + } + Assert.Equal(2, count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int count = 0; + + foreach (string data in MyData) + { + Assert.Equal(expected[count], data); + count++; + } + + Assert.Equal(2, count); + } + } + + public class TestClassWithGenericIListT : ITestClass + { + public IList MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + Assert.Equal("Hello", MyData[0]); + Assert.Equal("World", MyData[1]); + Assert.Equal(2, MyData.Count); + } + } + + public class TestClassWithGenericICollectionT : ITestClass + { + public ICollection MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int i = 0; + + foreach (string data in MyData) + { + Assert.Equal(expected[i++], data); + } + + Assert.Equal(2, MyData.Count); + } + } + + public class TestClassWithGenericIReadOnlyCollectionT : ITestClass + { + public IReadOnlyCollection MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + string[] expected = { "Hello", "World" }; + int i = 0; + + foreach (string data in MyData) + { + Assert.Equal(expected[i++], data); + } + + Assert.Equal(2, MyData.Count); + } + } + + public class TestClassWithGenericIReadOnlyListT : ITestClass + { + public IReadOnlyList MyData { get; set; } + + public static readonly byte[] s_data = Encoding.UTF8.GetBytes( + @"{" + + @"""MyData"":[" + + @"""Hello""," + + @"""World""" + + @"]" + + @"}"); + + public void Initialize() + { + MyData = new List + { + "Hello", + "World" + }; + Assert.Equal(2, MyData.Count); + } + + public void Verify() + { + Assert.Equal("Hello", MyData[0]); + Assert.Equal("World", MyData[1]); + Assert.Equal(2, MyData.Count); + } + } + public class SimpleDerivedTestClass : SimpleTestClass { } diff --git a/src/System.Text.Json/tests/Serialization/TestData.cs b/src/System.Text.Json/tests/Serialization/TestData.cs index 4cb507208ef6..de39bd4fa99c 100644 --- a/src/System.Text.Json/tests/Serialization/TestData.cs +++ b/src/System.Text.Json/tests/Serialization/TestData.cs @@ -20,8 +20,18 @@ public static IEnumerable ReadSuccessCases yield return new object[] { typeof(TestClassWithNestedObjectInner), TestClassWithNestedObjectInner.s_data }; yield return new object[] { typeof(TestClassWithNestedObjectOuter), TestClassWithNestedObjectOuter.s_data }; yield return new object[] { typeof(TestClassWithObjectArray), TestClassWithObjectArray.s_data }; + yield return new object[] { typeof(TestClassWithObjectIEnumerableT), TestClassWithObjectIEnumerableT.s_data }; + yield return new object[] { typeof(TestClassWithObjectIListT), TestClassWithObjectIListT.s_data }; + yield return new object[] { typeof(TestClassWithObjectICollectionT), TestClassWithObjectICollectionT.s_data }; + yield return new object[] { typeof(TestClassWithObjectIReadOnlyCollectionT), TestClassWithObjectIReadOnlyCollectionT.s_data }; + yield return new object[] { typeof(TestClassWithObjectIReadOnlyListT), TestClassWithObjectIReadOnlyListT.s_data }; yield return new object[] { typeof(TestClassWithStringArray), TestClassWithStringArray.s_data }; yield return new object[] { typeof(TestClassWithGenericList), TestClassWithGenericList.s_data }; + yield return new object[] { typeof(TestClassWithGenericIEnumerableT), TestClassWithGenericIEnumerableT.s_data }; + yield return new object[] { typeof(TestClassWithGenericIListT), TestClassWithGenericIListT.s_data }; + yield return new object[] { typeof(TestClassWithGenericICollectionT), TestClassWithGenericICollectionT.s_data }; + yield return new object[] { typeof(TestClassWithGenericIReadOnlyCollectionT), TestClassWithGenericIReadOnlyCollectionT.s_data }; + yield return new object[] { typeof(TestClassWithGenericIReadOnlyListT), TestClassWithGenericIReadOnlyListT.s_data }; } } public static IEnumerable WriteSuccessCases @@ -36,8 +46,18 @@ public static IEnumerable WriteSuccessCases yield return new object[] { new TestClassWithNestedObjectInner() }; yield return new object[] { new TestClassWithNestedObjectOuter() }; yield return new object[] { new TestClassWithObjectArray() }; + yield return new object[] { new TestClassWithObjectIEnumerableT() }; + yield return new object[] { new TestClassWithObjectIListT() }; + yield return new object[] { new TestClassWithObjectICollectionT() }; + yield return new object[] { new TestClassWithObjectIReadOnlyCollectionT() }; + yield return new object[] { new TestClassWithObjectIReadOnlyListT() }; yield return new object[] { new TestClassWithStringArray() }; yield return new object[] { new TestClassWithGenericList() }; + yield return new object[] { new TestClassWithGenericIEnumerableT() }; + yield return new object[] { new TestClassWithGenericIListT() }; + yield return new object[] { new TestClassWithGenericICollectionT() }; + yield return new object[] { new TestClassWithGenericIReadOnlyCollectionT() }; + yield return new object[] { new TestClassWithGenericIReadOnlyListT() }; } } } diff --git a/src/System.Text.Json/tests/Serialization/Value.ReadTests.cs b/src/System.Text.Json/tests/Serialization/Value.ReadTests.cs index 85967db0db7a..cb5ee2e5020e 100644 --- a/src/System.Text.Json/tests/Serialization/Value.ReadTests.cs +++ b/src/System.Text.Json/tests/Serialization/Value.ReadTests.cs @@ -253,6 +253,14 @@ public static void ReadObjectArray() i[1].Verify(); } + [Fact] + public static void ReadEmptyObjectArray() + { + SimpleTestClass[] data = JsonSerializer.Parse("[{}]"); + Assert.Equal(1, data.Length); + Assert.NotNull(data[0]); + } + [Fact] public static void ReadPrimitiveJaggedArray() { @@ -304,6 +312,291 @@ public static void ReadPrimitiveList() Assert.Equal(2, i[1]); } + [Fact] + public static void ReadIEnumerableTOfIEnumerableT() + { + IEnumerable> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IEnumerable ie in result) + { + foreach (int i in ie) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadIEnumerableTOfArray() + { + IEnumerable result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfIEnumerableT() + { + IEnumerable[] result = JsonSerializer.Parse[]> (Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IEnumerable arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadPrimitiveIEnumerableT() + { + IEnumerable result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } + + [Fact] + public static void ReadIListTOfIListT() + { + IList> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IList ie in result) + { + foreach (int i in ie) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadIListTOfArray() + { + IList result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfIListT() + { + IList[] result = JsonSerializer.Parse[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IList arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadPrimitiveIListT() + { + IList result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } + + [Fact] + public static void ReadICollectionTOfICollectionT() + { + ICollection> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ICollection ie in result) + { + foreach (int i in ie) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadICollectionTOfArray() + { + ICollection result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfICollectionT() + { + ICollection[] result = JsonSerializer.Parse[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (ICollection arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadPrimitiveICollectionT() + { + ICollection result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } + + [Fact] + public static void ReadIReadOnlyCollectionTOfIReadOnlyCollectionT() + { + IReadOnlyCollection> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IReadOnlyCollection ie in result) + { + foreach (int i in ie) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadIReadOnlyCollectionTOfArray() + { + IReadOnlyCollection result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfIReadOnlyCollectionT() + { + IReadOnlyCollection[] result = JsonSerializer.Parse[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IReadOnlyCollection arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadPrimitiveIReadOnlyCollectionT() + { + IReadOnlyCollection result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } + + [Fact] + public static void ReadIReadOnlyListTOfIReadOnlyListT() + { + IReadOnlyList> result = JsonSerializer.Parse>>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IReadOnlyList ie in result) + { + foreach (int i in ie) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadIReadOnlyListTOfArray() + { + IReadOnlyList result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (int[] arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadArrayOfIReadOnlyListT() + { + IReadOnlyList[] result = JsonSerializer.Parse[]>(Encoding.UTF8.GetBytes(@"[[1,2],[3,4]]")); + int expected = 1; + + foreach (IReadOnlyList arr in result) + { + foreach (int i in arr) + { + Assert.Equal(expected++, i); + } + } + } + + [Fact] + public static void ReadPrimitiveIReadOnlyListT() + { + IReadOnlyList result = JsonSerializer.Parse>(Encoding.UTF8.GetBytes(@"[1,2]")); + int expected = 1; + + foreach (int i in result) + { + Assert.Equal(expected++, i); + } + } + public class TestClassWithBadData { public TestChildClassWithBadData[] Children { get; set; } diff --git a/src/System.Text.Json/tests/Serialization/Value.WriteTests.cs b/src/System.Text.Json/tests/Serialization/Value.WriteTests.cs index 32a1974b4609..0ff8dbe4443e 100644 --- a/src/System.Text.Json/tests/Serialization/Value.WriteTests.cs +++ b/src/System.Text.Json/tests/Serialization/Value.WriteTests.cs @@ -83,6 +83,15 @@ public static void WriteObjectArray() } } + [Fact] + public static void WriteEmptyObjectArray() + { + object[] arr = new object[]{new object()}; + + string json = JsonSerializer.ToString(arr); + Assert.Equal("[{}]", json); + } + [Fact] public static void WritePrimitiveJaggedArray() { @@ -139,5 +148,235 @@ public static void WritePrimitiveList() string json = JsonSerializer.ToString(input); Assert.Equal("[1,2]", json); } + + [Fact] + public static void WriteIEnumerableTOfIEnumerableT() + { + IEnumerable> input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteIEnumerableTOfArray() + { + IEnumerable input = new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIEnumerableT() + { + IEnumerable[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIEnumerableT() + { + IEnumerable input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteIListTOfIListT() + { + IList> input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteIListTOfArray() + { + IList input = new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIListT() + { + IList[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIListT() + { + IList input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteICollectionTOfICollectionT() + { + ICollection> input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteICollectionTOfArray() + { + ICollection input = new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfICollectionT() + { + ICollection[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveICollectionT() + { + ICollection input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteIReadOnlyCollectionTOfIReadOnlyCollectionT() + { + IReadOnlyCollection> input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteIReadOnlyCollectionTOfArray() + { + IReadOnlyCollection input = new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIReadOnlyCollectionT() + { + IReadOnlyCollection[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIReadOnlyCollectionT() + { + IReadOnlyCollection input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } + + [Fact] + public static void WriteIReadOnlyListTOfIReadOnlyListT() + { + IReadOnlyList> input = new List> + { + new List() { 1, 2 }, + new List() { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteIReadOnlyListTOfArray() + { + IReadOnlyList input = new List + { + new int[] { 1, 2 }, + new int[] { 3, 4 } + }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WriteArrayOfIReadOnlyListT() + { + IReadOnlyList[] input = new List[2]; + input[0] = new List() { 1, 2 }; + input[1] = new List() { 3, 4 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[[1,2],[3,4]]", json); + } + + [Fact] + public static void WritePrimitiveIReadOnlyListT() + { + IReadOnlyList input = new List { 1, 2 }; + + string json = JsonSerializer.ToString(input); + Assert.Equal("[1,2]", json); + } } } diff --git a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj index cf5d2d6e0c4e..55b32d14aef5 100644 --- a/src/System.Text.Json/tests/System.Text.Json.Tests.csproj +++ b/src/System.Text.Json/tests/System.Text.Json.Tests.csproj @@ -7,7 +7,6 @@ CommonTest\System\IO\WrappedMemoryStream.cs - @@ -15,16 +14,17 @@ + - - + + @@ -33,12 +33,16 @@ + + + + @@ -52,7 +56,14 @@ + + + CommonTest\System\Buffers\ArrayBufferWriter.cs + + - + + + diff --git a/src/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs b/src/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs index 7d3a6e079566..965c863561c6 100644 --- a/src/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs +++ b/src/System.Text.Json/tests/Utf8JsonReaderTests.MultiSegment.cs @@ -744,5 +744,119 @@ private static void TestReadTokenWithExtra(ReadOnlySequence sequence, Json } }); } + + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommas))] + public static void JsonWithTrailingCommasMultiSegment_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(utf8, 1); + + { + JsonReaderState state = default; + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + } + + { + var state = new JsonReaderState(options: default); + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + } + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(sequence, state, allowTrailingCommas, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommas))] + public static void JsonWithTrailingCommasMultiSegment_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(utf8, 1); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(sequence, state, allowTrailingCommas, expectThrow: true); + } + } + + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommasAndComments))] + public static void JsonWithTrailingCommasAndCommentsMultiSegment_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(utf8, 1); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(sequence, state, allowTrailingCommas, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommasAndComments))] + public static void JsonWithTrailingCommasAndCommentsMultiSegment_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + ReadOnlySequence sequence = JsonTestHelper.GetSequence(utf8, 1); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(sequence, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(sequence, state, allowTrailingCommas, expectThrow: true); + } + } + + private static void TrailingCommasHelper(ReadOnlySequence utf8, JsonReaderState state, bool allow, bool expectThrow) + { + var reader = new Utf8JsonReader(utf8, isFinalBlock: true, state); + + Assert.Equal(allow, state.Options.AllowTrailingCommas); + Assert.Equal(allow, reader.CurrentState.Options.AllowTrailingCommas); + + if (expectThrow) + { + JsonTestHelper.AssertThrows(reader, (jsonReader) => + { + while (jsonReader.Read()) + ; + }); + } + else + { + while (reader.Read()) + ; + } + } } } diff --git a/src/System.Text.Json/tests/Utf8JsonReaderTests.cs b/src/System.Text.Json/tests/Utf8JsonReaderTests.cs index 831b45c98ae1..f7987db6c458 100644 --- a/src/System.Text.Json/tests/Utf8JsonReaderTests.cs +++ b/src/System.Text.Json/tests/Utf8JsonReaderTests.cs @@ -30,6 +30,7 @@ public static void DefaultUtf8JsonReader() Assert.Equal(0, json.CurrentState.BytesConsumed); Assert.Equal(default, json.CurrentState.Position); Assert.Equal(0, json.CurrentState.Options.MaxDepth); + Assert.False(json.CurrentState.Options.AllowTrailingCommas); Assert.Equal(JsonCommentHandling.Disallow, json.CurrentState.Options.CommentHandling); Assert.False(json.Read()); @@ -2120,6 +2121,233 @@ private static void TestReadTokenWithExtra(byte[] utf8, JsonCommentHandling comm }); } + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommas))] + public static void JsonWithTrailingCommas_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + { + JsonReaderState state = default; + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + } + + { + var state = new JsonReaderState(options: default); + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + } + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(utf8, state, allowTrailingCommas, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommas))] + public static void JsonWithTrailingCommas_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(utf8, state, allowTrailingCommas, expectThrow: true); + } + } + + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommasAndComments))] + public static void JsonWithTrailingCommasAndComments_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(utf8, state, allowTrailingCommas, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommasAndComments))] + public static void JsonWithTrailingCommasAndComments_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling }); + TrailingCommasHelper(utf8, state, allow: false, expectThrow: true); + + bool allowTrailingCommas = true; + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = allowTrailingCommas }); + TrailingCommasHelper(utf8, state, allowTrailingCommas, expectThrow: true); + } + } + + private static void TrailingCommasHelper(byte[] utf8, JsonReaderState state, bool allow, bool expectThrow) + { + var reader = new Utf8JsonReader(utf8, isFinalBlock: true, state); + + Assert.Equal(allow, state.Options.AllowTrailingCommas); + Assert.Equal(allow, reader.CurrentState.Options.AllowTrailingCommas); + + if (expectThrow) + { + JsonTestHelper.AssertThrows(reader, (jsonReader) => + { + while (jsonReader.Read()) + ; + }); + } + else + { + while (reader.Read()) + ; + } + } + + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommas))] + public static void PartialJsonWithTrailingCommas_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + { + JsonReaderState state = default; + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + } + + { + var state = new JsonReaderState(options: default); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + } + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = false }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = true }); + TrailingCommasHelperPartial(utf8, state, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithValidTrailingCommasAndComments))] + public static void PartialJsonWithTrailingCommasAndComments_Valid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = false }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = true }); + TrailingCommasHelperPartial(utf8, state, expectThrow: false); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommas))] + public static void PartialJsonWithTrailingCommas_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = false }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = true }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + } + } + + [Theory] + [MemberData(nameof(JsonWithInvalidTrailingCommasAndComments))] + public static void PartialJsonWithTrailingCommasAndComments_Invalid(string jsonString) + { + byte[] utf8 = Encoding.UTF8.GetBytes(jsonString); + + foreach (JsonCommentHandling commentHandling in Enum.GetValues(typeof(JsonCommentHandling))) + { + if (commentHandling == JsonCommentHandling.Disallow) + { + continue; + } + + var state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = false }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + + state = new JsonReaderState(options: new JsonReaderOptions { CommentHandling = commentHandling, AllowTrailingCommas = true }); + TrailingCommasHelperPartial(utf8, state, expectThrow: true); + } + } + + private static void TrailingCommasHelperPartial(byte[] utf8, JsonReaderState state, bool expectThrow) + { + if (expectThrow) + { + Assert.Throws(() => PartialReaderLoop(utf8, state)); + } + else + { + PartialReaderLoop(utf8, state); + } + } + + private static void PartialReaderLoop(byte[] utf8, JsonReaderState state) + { + for (int i = 0; i < utf8.Length; i++) + { + JsonReaderState stateCopy = state; + PartialReaderLoop(utf8, stateCopy, i); + } + } + + private static void PartialReaderLoop(byte[] utf8, JsonReaderState state, int splitLocation) + { + var reader = new Utf8JsonReader(utf8.AsSpan(0, splitLocation), isFinalBlock: false, state); + while (reader.Read()) + ; + + long consumed = reader.BytesConsumed; + reader = new Utf8JsonReader(utf8.AsSpan((int)consumed), isFinalBlock: true, reader.CurrentState); + while (reader.Read()) + ; + } + public static IEnumerable TestCases { get @@ -2345,6 +2573,116 @@ public static IEnumerable JsonTokenWithExtraValueAndComments } } + public static IEnumerable JsonWithValidTrailingCommas + { + get + { + return new List + { + new object[] {"{\"name\": \"value\",}"}, + new object[] {"{\"name\": [],}"}, + new object[] {"{\"name\": 1,}"}, + new object[] {"{\"name\": true,}"}, + new object[] {"{\"name\": false,}"}, + new object[] {"{\"name\": null,}"}, + new object[] {"{\"name\": [{},],}"}, + new object[] {"{\"first\" : \"value\", \"name\": [{},], \"last\":2 ,}"}, + new object[] {"{\"prop\":{\"name\": 1,\"last\":2,},}"}, + new object[] {"{\"prop\":[1,2,],}"}, + new object[] {"[\"value\",]"}, + new object[] {"[1,]"}, + new object[] {"[true,]"}, + new object[] {"[false,]"}, + new object[] {"[null,]"}, + new object[] {"[{},]"}, + new object[] {"[{\"name\": [],},]"}, + new object[] {"[1, {\"name\": [],},2 , ]"}, + new object[] {"[[1,2,],]"}, + new object[] {"[{\"name\": 1,\"last\":2,},]"}, + }; + } + } + + public static IEnumerable JsonWithValidTrailingCommasAndComments + { + get + { + return new List + { + new object[] {"{\"name\": \"value\"/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": []/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": 1/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": true/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": false/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": null/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": [{},]/*comment*/,/*comment*/}"}, + new object[] {"{\"first\" : \"value\", \"name\": [{},], \"last\":2 /*comment*/,/*comment*/}"}, + new object[] {"{\"prop\":{\"name\": 1,\"last\":2,}/*comment*/,}"}, + new object[] {"{\"prop\":[1,2,]/*comment*/,}"}, + new object[] {"{\"prop\":1,/*comment*/}"}, + new object[] {"[\"value\"/*comment*/,/*comment*/]"}, + new object[] {"[1/*comment*/,/*comment*/]"}, + new object[] {"[true/*comment*/,/*comment*/]"}, + new object[] {"[false/*comment*/,/*comment*/]"}, + new object[] {"[null/*comment*/,/*comment*/]"}, + new object[] {"[{}/*comment*/,/*comment*/]"}, + new object[] {"[{\"name\": [],}/*comment*/,/*comment*/]"}, + new object[] {"[1, {\"name\": [],},2 /*comment*/,/*comment*/ ]"}, + new object[] {"[[1,2,]/*comment*/,]"}, + new object[] {"[{\"name\": 1,\"last\":2,}/*comment*/,]"}, + new object[] {"[1,/*comment*/]"}, + }; + } + } + + public static IEnumerable JsonWithInvalidTrailingCommas + { + get + { + return new List + { + new object[] {","}, + new object[] {" , "}, + new object[] {"{},"}, + new object[] {"[],"}, + new object[] {"1,"}, + new object[] {"true,"}, + new object[] {"false,"}, + new object[] {"null,"}, + new object[] {"{,}"}, + new object[] {"{\"name\": 1,,}"}, + new object[] {"{\"name\": 1,,\"last\":2,}"}, + new object[] {"[,]"}, + new object[] {"[1,,]"}, + new object[] {"[1,,2,]"}, + }; + } + } + + public static IEnumerable JsonWithInvalidTrailingCommasAndComments + { + get + { + return new List + { + new object[] {"/*comment*/ ,/*comment*/"}, + new object[] {" /*comment*/ , /*comment*/ "}, + new object[] {"{}/*comment*/,/*comment*/"}, + new object[] {"[]/*comment*/,/*comment*/"}, + new object[] {"1/*comment*/,/*comment*/"}, + new object[] {"true/*comment*/,/*comment*/"}, + new object[] {"false/*comment*/,/*comment*/"}, + new object[] {"null/*comment*/,/*comment*/"}, + new object[] {"{/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": 1/*comment*/,/*comment*/,/*comment*/}"}, + new object[] {"{\"name\": 1,/*comment*/,\"last\":2,}"}, + new object[] {"[/*comment*/,/*comment*/]"}, + new object[] {"[1/*comment*/,/*comment*/,/*comment*/]"}, + new object[] {"[1,/*comment*/,2,]"}, + }; + } + } + public static IEnumerable InvalidJsonStrings { get diff --git a/src/System.Text.Json/tests/Utf8JsonWriterTests.cs b/src/System.Text.Json/tests/Utf8JsonWriterTests.cs index 32bf61c9c4b1..0ce3e8824868 100644 --- a/src/System.Text.Json/tests/Utf8JsonWriterTests.cs +++ b/src/System.Text.Json/tests/Utf8JsonWriterTests.cs @@ -3,9 +3,11 @@ // See the LICENSE file in the project root for more information. using Xunit; +using System.Buffers; using System.IO; using Newtonsoft.Json; using System.Globalization; +using System.Threading.Tasks; namespace System.Text.Json.Tests { @@ -20,21 +22,261 @@ public class Utf8JsonWriterTests [InlineData(false, false)] public void NullCtor(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - try - { - var jsonUtf8 = new Utf8JsonWriter(null); - Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in."); - } - catch (ArgumentNullException) { } + Assert.Throws(() => new Utf8JsonWriter((Stream)null)); + Assert.Throws(() => new Utf8JsonWriter((IBufferWriter)null)); + Assert.Throws(() => new Utf8JsonWriter((Stream)null, options)); + Assert.Throws(() => new Utf8JsonWriter((IBufferWriter)null, options)); + } - try - { - var jsonUtf8 = new Utf8JsonWriter(null, state); - Assert.True(false, "Expected ArgumentNullException to be thrown when null IBufferWriter is passed in."); - } - catch (ArgumentNullException) { } + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void CantWriteToNonWritableStream(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var stream = new MemoryStream(); + stream.Dispose(); + + Assert.Throws(() => new Utf8JsonWriter(stream)); + Assert.Throws(() => new Utf8JsonWriter(stream, options)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InitialState(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writer = new Utf8JsonWriter(stream, options); + Assert.Equal(0, writer.BytesCommitted); + Assert.Equal(0, writer.BytesPending); + Assert.Equal(0, writer.CurrentDepth); + Assert.Equal(formatted, writer.Options.Indented); + Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(0, stream.Position); + + var output = new FixedSizedBufferWriter(0); + writer = new Utf8JsonWriter(output, options); + Assert.Equal(0, writer.BytesCommitted); + Assert.Equal(0, writer.BytesPending); + Assert.Equal(0, writer.CurrentDepth); + Assert.Equal(formatted, writer.Options.Indented); + Assert.Equal(skipValidation, writer.Options.SkipValidation); + Assert.Equal(0, output.FormattedCount); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void Reset(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + writeToStream.Reset(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWritten = stream.Position; + writeToStream.Flush(); + Assert.Equal(previousWritten, stream.Position); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + writeToIBW.Reset(); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWritten = output.FormattedCount; + writeToIBW.Flush(); + Assert.Equal(previousWritten, output.FormattedCount); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ResetWithSameOutput(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + writeToStream.Reset(stream); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWritten = stream.Position; + writeToStream.Flush(); + Assert.Equal(previousWritten, stream.Position); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.NotEqual(previousWritten, stream.Position); + Assert.Equal("11", Encoding.UTF8.GetString(stream.ToArray())); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + writeToIBW.Reset(output); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWritten = output.FormattedCount; + writeToIBW.Flush(); + Assert.Equal(previousWritten, output.FormattedCount); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.NotEqual(previousWritten, output.FormattedCount); + Assert.Equal("11", Encoding.UTF8.GetString(output.Formatted)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void ResetChangeOutputMode(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + + var output = new FixedSizedBufferWriter(32); + writeToStream.Reset(output); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(0, writeToStream.CurrentDepth); + Assert.Equal(formatted, writeToStream.Options.Indented); + Assert.Equal(skipValidation, writeToStream.Options.SkipValidation); + Assert.True(stream.Position != 0); + + long previousWrittenStream = stream.Position; + long previousWrittenIBW = output.FormattedCount; + Assert.Equal(0, previousWrittenIBW); + writeToStream.Flush(); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + + writeToStream.WriteNumberValue(1); + writeToStream.Flush(); + + Assert.True(writeToStream.BytesCommitted != 0); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.True(output.FormattedCount != 0); + + output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + + stream = new MemoryStream(); + writeToIBW.Reset(stream); + Assert.Equal(0, writeToIBW.BytesCommitted); + Assert.Equal(0, writeToIBW.BytesPending); + Assert.Equal(0, writeToIBW.CurrentDepth); + Assert.Equal(formatted, writeToIBW.Options.Indented); + Assert.Equal(skipValidation, writeToIBW.Options.SkipValidation); + Assert.True(output.FormattedCount != 0); + + previousWrittenStream = stream.Position; + previousWrittenIBW = output.FormattedCount; + Assert.Equal(0, previousWrittenStream); + writeToIBW.Flush(); + Assert.Equal(previousWrittenStream, stream.Position); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + + writeToIBW.WriteNumberValue(1); + writeToIBW.Flush(); + + Assert.True(writeToIBW.BytesCommitted != 0); + Assert.Equal(previousWrittenIBW, output.FormattedCount); + Assert.True(stream.Position != 0); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidReset(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + + Assert.Throws(() => writeToStream.Reset((Stream)null)); + Assert.Throws(() => writeToStream.Reset((IBufferWriter)null)); + + stream.Dispose(); + + Assert.Throws(() => writeToStream.Reset(stream)); + + var output = new FixedSizedBufferWriter(32); + var writeToIBW = new Utf8JsonWriter(output, options); + + Assert.Throws(() => writeToIBW.Reset((Stream)null)); + Assert.Throws(() => writeToIBW.Reset((IBufferWriter)null)); + + Assert.Throws(() => writeToIBW.Reset(stream)); } [Theory] @@ -44,25 +286,41 @@ public void NullCtor(bool formatted, bool skipValidation) [InlineData(false, false)] public void FlushEmpty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(0); - try - { - var jsonUtf8 = new Utf8JsonWriter(output, state); - jsonUtf8.Flush(); - WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload."); - } - catch (InvalidOperationException) { } - output = new FixedSizedBufferWriter(10); - try - { - var jsonUtf8 = new Utf8JsonWriter(output, state); - jsonUtf8.WriteCommentValue("hi"); - jsonUtf8.Flush(); - WriterDidNotThrow(skipValidation, "Expected InvalidOperationException to be thrown when calling Flush on an empty JSON payload."); - } - catch (InvalidOperationException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.Flush(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.Flush(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, stream.Position); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task FlushEmptyAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(0); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + await jsonUtf8.FlushAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + await writeToStream.FlushAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, stream.Position); } [Theory] @@ -72,16 +330,39 @@ public void FlushEmpty(bool formatted, bool skipValidation) [InlineData(false, false)] public void FlushMultipleTimes(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); jsonUtf8.Flush(); Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); jsonUtf8.Flush(); Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + writeToStream.Flush(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + writeToStream.Flush(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); } [Theory] @@ -89,20 +370,275 @@ public void FlushMultipleTimes(bool formatted, bool skipValidation) [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] - public void InvalidBufferWriter(bool formatted, bool skipValidation) + public async Task FlushMultipleTimesAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.FlushAsync(); + Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + await jsonUtf8.FlushAsync(); + Assert.Equal(2, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + await writeToStream.FlushAsync(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + await writeToStream.FlushAsync(); + Assert.Equal(2, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(2, stream.Position); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void DisposeAutoFlushes(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + jsonUtf8.Dispose(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, stream.Position); + writeToStream.Dispose(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, stream.Position); + } + +#if !netstandard + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task DisposeAutoFlushesAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.DisposeAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(2, output.FormattedCount); + + var stream = new MemoryStream(); + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + writeToStream.WriteEndObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, stream.Position); + await writeToStream.DisposeAsync(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(2, stream.Position); + } +#endif + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void UseAfterDisposeInvalid(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + jsonUtf8.Dispose(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(1, output.FormattedCount); + Assert.Throws(() => jsonUtf8.Flush()); + jsonUtf8.Dispose(); + Assert.Throws(() => jsonUtf8.Flush()); + + jsonUtf8.Reset(); + + var stream = new MemoryStream(); + Assert.Throws(() => jsonUtf8.Reset(stream)); + + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(1, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + writeToStream.Dispose(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(1, stream.Position); + Assert.Throws(() => writeToStream.Flush()); + writeToStream.Dispose(); + Assert.Throws(() => writeToStream.Flush()); + + writeToStream.Reset(); + + Assert.Throws(() => jsonUtf8.Reset(output)); + } + +#if !netstandard + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task UseAfterDisposeInvalidAsync(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new FixedSizedBufferWriter(10); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); + Assert.Equal(0, output.FormattedCount); + await jsonUtf8.DisposeAsync(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); + Assert.Equal(1, output.FormattedCount); + await Assert.ThrowsAsync(() => jsonUtf8.FlushAsync()); + await jsonUtf8.DisposeAsync(); + await Assert.ThrowsAsync(() => jsonUtf8.FlushAsync()); + + jsonUtf8.Reset(); + + var stream = new MemoryStream(); + Assert.Throws(() => jsonUtf8.Reset(stream)); + + var writeToStream = new Utf8JsonWriter(stream, options); + writeToStream.WriteStartObject(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(1, writeToStream.BytesPending); + Assert.Equal(0, stream.Position); + await writeToStream.DisposeAsync(); + Assert.Equal(0, writeToStream.BytesCommitted); + Assert.Equal(0, writeToStream.BytesPending); + Assert.Equal(1, stream.Position); + await Assert.ThrowsAsync(() => writeToStream.FlushAsync()); + await writeToStream.DisposeAsync(); + await Assert.ThrowsAsync(() => writeToStream.FlushAsync()); + + writeToStream.Reset(); + + Assert.Throws(() => jsonUtf8.Reset(output)); + } +#endif + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidBufferWriter(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new InvalidBufferWriter(); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); - try + Assert.Throws(() => jsonUtf8.WriteNumberValue((ulong)12345678901)); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public async Task WriteLargeToStream(bool formatted, bool skipValidation) + { + var stream = new MemoryStream(); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + await WriteLargeToStreamHelper(stream, options); + + string expectedString = GetExpectedLargeString(formatted); + string actualString = Encoding.UTF8.GetString(stream.ToArray()); + + Assert.Equal(expectedString, actualString); + } + + private static async Task WriteLargeToStreamHelper(Stream stream, JsonWriterOptions options) + { + const int SyncWriteThreshold = 25_000; + +#if !netstandard + await +#endif + using var jsonUtf8 = new Utf8JsonWriter(stream, options); + + byte[] utf8String = Encoding.UTF8.GetBytes("some string 1234"); + + jsonUtf8.WriteStartArray(); + for (int i = 0; i < 10_000; i++) { - jsonUtf8.WriteNumberValue((ulong)12345678901); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); + jsonUtf8.WriteStringValue(utf8String); + if (jsonUtf8.BytesPending > SyncWriteThreshold) + { + await jsonUtf8.FlushAsync(); + } } - catch (ArgumentException) { } + jsonUtf8.WriteEndArray(); + } + + private static string GetExpectedLargeString(bool prettyPrint) + { + var ms = new MemoryStream(); + TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); + + var json = new JsonTextWriter(streamWriter) + { + Formatting = prettyPrint ? Formatting.Indented : Formatting.None + }; + + json.WriteStartArray(); + for (int i = 0; i < 10_000; i++) + { + json.WriteValue("some string 1234"); + } + json.WriteEnd(); + + json.Flush(); + + return Encoding.UTF8.GetString(ms.ToArray()); } [Theory] @@ -112,23 +648,16 @@ public void InvalidBufferWriter(bool formatted, bool skipValidation) [InlineData(false, false)] public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(37); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); Guid guid = Guid.NewGuid(); - try - { - jsonUtf8.WriteStringValue(guid); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(guid)); - output = new FixedSizedBufferWriter(39); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(41); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(guid); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -144,23 +673,16 @@ public void FixedSizeBufferWriter_Guid(bool formatted, bool skipValidation) [InlineData(false, false)] public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(20); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); var date = new DateTime(2019, 1, 1); - try - { - jsonUtf8.WriteStringValue(date); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(date)); - output = new FixedSizedBufferWriter(21); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(38); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(date); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -176,23 +698,16 @@ public void FixedSizeBufferWriter_DateTime(bool formatted, bool skipValidation) [InlineData(false, false)] public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var output = new FixedSizedBufferWriter(26); - var jsonUtf8 = new Utf8JsonWriter(output, state); - + var jsonUtf8 = new Utf8JsonWriter(output, options); DateTimeOffset date = new DateTime(2019, 1, 1); - try - { - jsonUtf8.WriteStringValue(date); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteStringValue(date)); - output = new FixedSizedBufferWriter(27); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(38); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStringValue(date); jsonUtf8.Flush(); string actualStr = Encoding.UTF8.GetString(output.Formatted); @@ -208,14 +723,15 @@ public void FixedSizeBufferWriter_DateTimeOffset(bool formatted, bool skipValida [InlineData(false, false)] public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; var random = new Random(42); for (int i = 0; i < 1_000; i++) { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = JsonTestHelper.NextDecimal(random, 78E14, -78E14); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -227,10 +743,9 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) for (int i = 0; i < 1_000; i++) { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = JsonTestHelper.NextDecimal(random, 1_000_000, -1_000_000); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -241,10 +756,9 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) } { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = 9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -255,10 +769,9 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) } { - var output = new FixedSizedBufferWriter(31); + var output = new FixedSizedBufferWriter(34); decimal value = -9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -271,18 +784,12 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) { var output = new FixedSizedBufferWriter(30); decimal value = -0.9999999999999999999999999999m; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); - try - { - jsonUtf8.WriteNumberValue(value); - Assert.True(false, "Expected ArgumentException to be thrown when IBufferWriter doesn't have enough space."); - } - catch (ArgumentException) { } + Assert.Throws(() => jsonUtf8.WriteNumberValue(value)); - output = new FixedSizedBufferWriter(31); - jsonUtf8 = new Utf8JsonWriter(output, state); + output = new FixedSizedBufferWriter(34); + jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteNumberValue(value); jsonUtf8.Flush(); @@ -300,181 +807,256 @@ public void FixedSizeBufferWriter_Decimal(bool formatted, bool skipValidation) [InlineData(false, false)] public void InvalidJsonMismatch(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) + { + jsonUtf8.WriteStartArray("property at start"); + } + else { - jsonUtf8.WriteStartArray("property at start", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray("property at start")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + if (skipValidation) { - jsonUtf8.WriteStartObject("property at start", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject("property at start"); + } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("property at start")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray("property inside array", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartArray("property inside array"); + } + else + { + Assert.Throws(() => jsonUtf8.WriteStartArray("property inside array")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteStringValue("key"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStringValue("key")); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); jsonUtf8.WriteString("key", "value"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteString("key", "value")); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("some object"); + jsonUtf8.WriteEndObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); + jsonUtf8.WriteStartObject("some object"); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("some object")); + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartArray("test array"); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray("test array", escape: false); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + if (skipValidation) { - jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); - jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("test object"); + if (skipValidation) { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("test object", escape: false); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidJsonIncomplete(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); + + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteEndArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartObject("some object"); + Assert.True(jsonUtf8.CurrentDepth != 0); + + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartArray(); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteEndObject(); + Assert.True(jsonUtf8.CurrentDepth != 0); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + jsonUtf8.WriteStartArray("test array"); + jsonUtf8.WriteEndArray(); + Assert.True(jsonUtf8.CurrentDepth != 0); } [Theory] @@ -482,160 +1064,98 @@ public void InvalidJsonMismatch(bool formatted, bool skipValidation) [InlineData(true, false)] [InlineData(false, true)] [InlineData(false, false)] - public void InvalidJsonIncomplete(bool formatted, bool skipValidation) + public void InvalidJsonPrimitive(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var output = new ArrayBufferWriter(1024); - - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteNumberValue(12345); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteNumberValue(12345)); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteEndArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray()); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStartObject("some object", escape: false); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject(); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray("test array", escape: false); - jsonUtf8.WriteEndArray(); - jsonUtf8.Flush(isFinalBlock: true); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartObject()); } - catch (InvalidOperationException) { } - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void InvalidJsonPrimitive(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteNumberValue(12345); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartArray("property name"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartArray(); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartArray("property name")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteStartObject("property name"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartArray("property name", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteStartObject("property name")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteStartObject("property name", escape: false); - WriterDidNotThrow(skipValidation); + jsonUtf8.WriteString("property name", "value"); } - catch (InvalidOperationException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try + else { - jsonUtf8.WriteNumberValue(12345); - jsonUtf8.WriteString("property name", "value", escape: false); - WriterDidNotThrow(skipValidation); + Assert.Throws(() => jsonUtf8.WriteString("property name", "value")); } - catch (InvalidOperationException) { } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } + else + { + Assert.Throws(() => jsonUtf8.WriteEndArray()); + } - jsonUtf8 = new Utf8JsonWriter(output, state); - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteNumberValue(12345); + if (skipValidation) { - jsonUtf8.WriteNumberValue(12345); jsonUtf8.WriteEndObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteEndObject()); + } } [Theory] @@ -645,113 +1165,50 @@ public void InvalidJsonPrimitive(bool formatted, bool skipValidation) [InlineData(false, false)] public void InvalidNumbersJson(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } - - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(double.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(double.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteNumberValue(float.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + Assert.Throws(() => jsonUtf8.WriteNumberValue(float.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.NegativeInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", double.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.PositiveInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", double.NaN)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.NegativeInfinity); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.PositiveInfinity)); - jsonUtf8 = new Utf8JsonWriter(output, state); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteNumber("name", float.NaN); - Assert.True(false, "Expected ArgumentException to be thrown for unsupported number values."); - } - catch (ArgumentException) { } + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.NegativeInfinity)); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteNumber("name", float.NaN)); } [Theory] @@ -759,21 +1216,19 @@ public void InvalidNumbersJson(bool formatted, bool skipValidation) [InlineData(false)] public void InvalidJsonContinueShouldSucceed(bool formatted) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); for (int i = 0; i < 100; i++) + { jsonUtf8.WriteEndArray(); + } jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - var sb = new StringBuilder(); for (int i = 0; i < 100; i++) { @@ -786,9 +1241,7 @@ public void InvalidJsonContinueShouldSucceed(bool formatted) sb.Append(Environment.NewLine); sb.Append("[]"); - Assert.Equal(sb.ToString(), actualStr); - - output.Dispose(); + AssertContents(sb.ToString(), output); } [Theory] @@ -798,22 +1251,17 @@ public void InvalidJsonContinueShouldSucceed(bool formatted) [InlineData(false, false)] public void WritingTooDeep(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); - var jsonUtf8 = new Utf8JsonWriter(output, state); - try + for (int i = 0; i < 1000; i++) { - for (int i = 0; i < 1001; i++) - { - jsonUtf8.WriteStartArray(); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartArray(); } - catch (InvalidOperationException) { } - - output.Dispose(); + Assert.Equal(1000, jsonUtf8.CurrentDepth); + Assert.Throws(() => jsonUtf8.WriteStartArray()); } [Theory] @@ -823,37 +1271,25 @@ public void WritingTooDeep(bool formatted, bool skipValidation) [InlineData(false, false)] public void WritingTooDeepProperty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - try + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 999; i++) { - jsonUtf8.WriteStartObject(); - for (int i = 0; i < 1000; i++) - { - jsonUtf8.WriteStartArray("name"); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartObject("name"); } - catch (InvalidOperationException) { } + Assert.Equal(1000, jsonUtf8.CurrentDepth); + Assert.Throws(() => jsonUtf8.WriteStartArray("name")); - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 999; i++) { - jsonUtf8.WriteStartObject(); - for (int i = 0; i < 1000; i++) - { - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name")); - } - Assert.True(false, "Expected InvalidOperationException to be thrown for depth >= 1000."); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("name")); } - catch (InvalidOperationException) { } - - output.Dispose(); + Assert.Throws(() => jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("name"))); } [ConditionalTheory(nameof(IsX64))] @@ -864,14 +1300,8 @@ public void WritingTooDeepProperty(bool formatted, bool skipValidation) [InlineData(false, false)] public void WritingTooLargeProperty(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(1024); - - var jsonUtf8 = new Utf8JsonWriter(output, state); - - Span key; - Span keyChars; + byte[] key; + char[] keyChars; try { @@ -883,28 +1313,19 @@ public void WritingTooLargeProperty(bool formatted, bool skipValidation) return; } - key.Fill((byte)'a'); - keyChars.Fill('a'); - - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray(keyChars); - Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {keyChars.Length}"); - } - catch (ArgumentException) { } + key.AsSpan().Fill((byte)'a'); + keyChars.AsSpan().Fill('a'); - jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - try - { - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteStartArray(key); - Assert.True(false, $"Expected ArgumentException for property too large wasn't thrown. PropertyLength: {key.Length}"); - } - catch (ArgumentException) { } + var jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteStartArray(keyChars)); - output.Dispose(); + jsonUtf8 = new Utf8JsonWriter(output, options); + jsonUtf8.WriteStartObject(); + Assert.Throws(() => jsonUtf8.WriteStartArray(key)); } [Theory] @@ -916,24 +1337,16 @@ public void WriteSingleValue(bool formatted, bool skipValidation) { string expectedStr = "123456789012345"; - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 3; i++) - { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - jsonUtf8.WriteNumberValue(123456789012345); - - jsonUtf8.Flush(); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + jsonUtf8.WriteNumberValue(123456789012345); - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); + jsonUtf8.Flush(); - output.Dispose(); - } + AssertContents(expectedStr, output); } [Theory] @@ -943,58 +1356,144 @@ public void WriteSingleValue(bool formatted, bool skipValidation) [InlineData(false, false)] public void WriteHelloWorld(bool formatted, bool skipValidation) { - string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted); + string propertyName = "message"; + string value = "Hello, World!"; + string expectedStr = GetHelloWorldExpectedString(prettyPrint: formatted, propertyName, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 9; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString("message", "Hello, World!", escape: false); + jsonUtf8.WriteString("message", "Hello, World!"); + jsonUtf8.WriteString("message", "Hello, World!"); break; case 1: - jsonUtf8.WriteString("message", "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString("message", "Hello, World!".AsSpan()); + jsonUtf8.WriteString("message", "Hello, World!".AsSpan()); break; case 2: - jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString("message", Encoding.UTF8.GetBytes("Hello, World!")); break; case 3: - jsonUtf8.WriteString("message".AsSpan(), "Hello, World!", escape: false); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!"); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!"); break; case 4: - jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan()); + jsonUtf8.WriteString("message".AsSpan(), "Hello, World!".AsSpan()); break; case 5: - jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString("message".AsSpan(), Encoding.UTF8.GetBytes("Hello, World!")); break; case 6: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!", escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!"); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!"); break; case 7: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan(), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan()); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), "Hello, World!".AsSpan()); break; case 8: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!"), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!")); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes("message"), Encoding.UTF8.GetBytes("Hello, World!")); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); + AssertContents(expectedStr, output); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteHelloWorldEscaped(bool formatted, bool skipValidation) + { + string propertyName = "mess> propertyNameSpan = propertyName.AsSpan(); + ReadOnlySpan valueSpan = value.AsSpan(); + ReadOnlySpan propertyNameSpanUtf8 = Encoding.UTF8.GetBytes(propertyName); + ReadOnlySpan valueSpanUtf8 = Encoding.UTF8.GetBytes(value); + + for (int i = 0; i < 9; i++) + { + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + jsonUtf8.WriteStartObject(); + + switch (i) + { + case 0: + jsonUtf8.WriteString(propertyName, value); + jsonUtf8.WriteString(propertyName, value); + break; + case 1: + jsonUtf8.WriteString(propertyName, valueSpan); + jsonUtf8.WriteString(propertyName, valueSpan); + break; + case 2: + jsonUtf8.WriteString(propertyName, valueSpanUtf8); + jsonUtf8.WriteString(propertyName, valueSpanUtf8); + break; + case 3: + jsonUtf8.WriteString(propertyNameSpan, value); + jsonUtf8.WriteString(propertyNameSpan, value); + break; + case 4: + jsonUtf8.WriteString(propertyNameSpan, valueSpan); + jsonUtf8.WriteString(propertyNameSpan, valueSpan); + break; + case 5: + jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8); + jsonUtf8.WriteString(propertyNameSpan, valueSpanUtf8); + break; + case 6: + jsonUtf8.WriteString(propertyNameSpanUtf8, value); + jsonUtf8.WriteString(propertyNameSpanUtf8, value); + break; + case 7: + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan); + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpan); + break; + case 8: + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8); + jsonUtf8.WriteString(propertyNameSpanUtf8, valueSpanUtf8); + break; + } - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); - output.Dispose(); + AssertContents(expectedStr, output); } + + // Verify that escaping does not change the input strings/spans. + Assert.Equal("mess>< World!", value); + Assert.True(propertyName.AsSpan().SequenceEqual(propertyNameSpan)); + Assert.True(value.AsSpan().SequenceEqual(valueSpan)); + Assert.True(Encoding.UTF8.GetBytes(propertyName).AsSpan().SequenceEqual(propertyNameSpanUtf8)); + Assert.True(Encoding.UTF8.GetBytes(value).AsSpan().SequenceEqual(valueSpanUtf8)); } [Theory] @@ -1004,32 +1503,32 @@ public void WriteHelloWorld(bool formatted, bool skipValidation) [InlineData(false, false)] public void WritePartialHelloWorld(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); Assert.Equal(0, jsonUtf8.BytesCommitted); - Assert.Equal(1, jsonUtf8.BytesWritten); + Assert.Equal(1, jsonUtf8.BytesPending); jsonUtf8.WriteString("message", "Hello, World!"); - Assert.Equal(16, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesCommitted); if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesWritten); + Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else - Assert.Equal(26, jsonUtf8.BytesWritten); + Assert.Equal(26, jsonUtf8.BytesPending); - jsonUtf8.Flush(isFinalBlock: false); + jsonUtf8.Flush(); if (formatted) Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(26, jsonUtf8.BytesCommitted); - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); + Assert.Equal(0, jsonUtf8.BytesPending); jsonUtf8.WriteString("message", "Hello, World!"); jsonUtf8.WriteEndObject(); @@ -1040,83 +1539,18 @@ public void WritePartialHelloWorld(bool formatted, bool skipValidation) Assert.Equal(26, jsonUtf8.BytesCommitted); if (formatted) - Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesWritten); // new lines, indentation, white space + Assert.Equal(27 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesPending); // new lines, indentation, white space else - Assert.Equal(53, jsonUtf8.BytesWritten); + Assert.Equal(27, jsonUtf8.BytesPending); - jsonUtf8.Flush(isFinalBlock: true); + jsonUtf8.Flush(); if (formatted) Assert.Equal(53 + (2 * 2) + (3 * Environment.NewLine.Length) + (1 * 2), jsonUtf8.BytesCommitted); // new lines, indentation, white space else Assert.Equal(53, jsonUtf8.BytesCommitted); - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); - - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - state = jsonUtf8.GetCurrentState(); - Assert.Equal(jsonUtf8.BytesCommitted, state.BytesCommitted); - Assert.Equal(jsonUtf8.BytesWritten, state.BytesWritten); - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialHelloWorldSaveState(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - Assert.Equal(0, jsonUtf8.CurrentDepth); - jsonUtf8.WriteStartObject(); - Assert.Equal(1, jsonUtf8.CurrentDepth); - jsonUtf8.Flush(isFinalBlock: false); - - state = jsonUtf8.GetCurrentState(); - - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - Assert.Equal(1, jsonUtf8.CurrentDepth); - - jsonUtf8.WriteString("message", "Hello, World!"); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - Assert.Equal(jsonUtf8.BytesCommitted, jsonUtf8.BytesWritten); - - if (formatted) - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, jsonUtf8.BytesCommitted); - else - Assert.Equal(26, jsonUtf8.BytesCommitted); - - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); - - state = jsonUtf8.GetCurrentState(); - - if (formatted) - { - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesCommitted); - Assert.Equal(26 + 2 + (2 * Environment.NewLine.Length) + 1, state.BytesWritten); - } - else - { - Assert.Equal(26, state.BytesCommitted); - Assert.Equal(26, state.BytesWritten); - } - - output.Dispose(); + Assert.Equal(0, jsonUtf8.BytesPending); } [Theory] @@ -1126,88 +1560,30 @@ public void WritePartialHelloWorldSaveState(bool formatted, bool skipValidation) [InlineData(false, false)] public void WriteInvalidPartialJson(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); - Assert.Equal(0, state.BytesCommitted); - Assert.Equal(0, state.BytesWritten); - - jsonUtf8.Flush(isFinalBlock: false); - - state = jsonUtf8.GetCurrentState(); + Assert.Equal(0, jsonUtf8.BytesCommitted); + Assert.Equal(1, jsonUtf8.BytesPending); - Assert.Equal(1, state.BytesCommitted); - Assert.Equal(1, state.BytesWritten); + jsonUtf8.Flush(); - jsonUtf8 = new Utf8JsonWriter(output, state); + Assert.Equal(1, jsonUtf8.BytesCommitted); + Assert.Equal(0, jsonUtf8.BytesPending); - try + if (skipValidation) { jsonUtf8.WriteStringValue("Hello, World!"); - WriterDidNotThrow(skipValidation); - } - catch (InvalidOperationException) { } - try - { jsonUtf8.WriteEndArray(); - WriterDidNotThrow(skipValidation); - } - catch (InvalidOperationException) { } - - output.Dispose(); - } - - [Theory] - [InlineData(true, true)] - [InlineData(true, false)] - [InlineData(false, true)] - [InlineData(false, false)] - public void WritePartialJsonSkipFlush(bool formatted, bool skipValidation) - { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); - - jsonUtf8.WriteStartObject(); - jsonUtf8.WriteString("message", "Hello, World!"); - - Assert.Equal(1, jsonUtf8.CurrentDepth); - - try - { - state = jsonUtf8.GetCurrentState(); - Assert.True(false, "Expected InvalidOperationException when trying to get current state without flushing first."); } - catch (InvalidOperationException) - { - - } - finally + else { - jsonUtf8.Flush(isFinalBlock: false); - state = jsonUtf8.GetCurrentState(); + Assert.Throws(() => jsonUtf8.WriteStringValue("Hello, World!")); + Assert.Throws(() => jsonUtf8.WriteEndArray()); } - - if (formatted) - Assert.Equal(26 + 2 + Environment.NewLine.Length + 1, state.BytesWritten); - else - Assert.Equal(26, state.BytesWritten); - - Assert.Equal(jsonUtf8.BytesWritten, jsonUtf8.BytesCommitted); - - jsonUtf8 = new Utf8JsonWriter(output, state); - Assert.Equal(1, jsonUtf8.CurrentDepth); - Assert.Equal(0, jsonUtf8.BytesWritten); - Assert.Equal(0, jsonUtf8.BytesCommitted); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - output.Dispose(); } [Theory] @@ -1218,10 +1594,9 @@ public void WritePartialJsonSkipFlush(bool formatted, bool skipValidation) public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) { { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); @@ -1229,43 +1604,33 @@ public void WriteInvalidDepthPartial(bool formatted, bool skipValidation) Assert.Equal(0, jsonUtf8.CurrentDepth); - state = jsonUtf8.GetCurrentState(); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + if (skipValidation) { jsonUtf8.WriteStartObject(); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject()); + } } { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - Assert.Equal(0, jsonUtf8.CurrentDepth); - state = jsonUtf8.GetCurrentState(); - - jsonUtf8 = new Utf8JsonWriter(output, state); - - try + if (skipValidation) { jsonUtf8.WriteStartObject("name"); - WriterDidNotThrow(skipValidation); } - catch (InvalidOperationException) { } - - output.Dispose(); + else + { + Assert.Throws(() => jsonUtf8.WriteStartObject("name")); + } } } @@ -1286,18 +1651,18 @@ public void WriteComments(bool formatted, bool skipValidation, string comment) { string expectedStr = GetCommentExpectedString(prettyPrint: formatted, comment); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); for (int j = 0; j < 10; j++) { - WriteCommentValue(ref jsonUtf8, i, comment); + WriteCommentValue(jsonUtf8, i, comment); } switch (i) @@ -1313,36 +1678,116 @@ public void WriteComments(bool formatted, bool skipValidation, string comment) break; } - WriteCommentValue(ref jsonUtf8, i, comment); + WriteCommentValue(jsonUtf8, i, comment); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } - private static void WriteCommentValue(ref Utf8JsonWriter jsonUtf8, int i, string comment) + private static void WriteCommentValue(Utf8JsonWriter jsonUtf8, int i, string comment) { switch (i) { case 0: - jsonUtf8.WriteCommentValue(comment, escape: false); + jsonUtf8.WriteCommentValue(comment); break; case 1: - jsonUtf8.WriteCommentValue(comment.AsSpan(), escape: false); + jsonUtf8.WriteCommentValue(comment.AsSpan()); break; case 2: - jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment), escape: false); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); break; } } + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteInvalidComment(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + string comment = "comment is */ invalid"; + + Assert.Throws(() => jsonUtf8.WriteCommentValue(comment)); + Assert.Throws(() => jsonUtf8.WriteCommentValue(comment.AsSpan())); + Assert.Throws(() => jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment))); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteCommentsInvalidTextAllowed(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(32); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + string comment = "comment is * / valid"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); + + comment = "comment is /* valid"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + jsonUtf8.WriteCommentValue(Encoding.UTF8.GetBytes(comment)); + + comment = "comment is / * valid even with unpaired surrogate \udc00 this part no longer visible"; + jsonUtf8.WriteCommentValue(comment); + jsonUtf8.WriteCommentValue(comment.AsSpan()); + + jsonUtf8.Flush(); + + // Explicitly skipping flushing here + var invalidUtf8 = new byte[2] { 0xc3, 0x28 }; + jsonUtf8.WriteCommentValue(invalidUtf8); + + string expectedStr = GetCommentExpectedString(prettyPrint: formatted); + AssertContents(expectedStr, output); + } + + private static string GetCommentExpectedString(bool prettyPrint) + { + var ms = new MemoryStream(); + TextWriter streamWriter = new StreamWriter(ms, new UTF8Encoding(false), 1024, true); + + var json = new JsonTextWriter(streamWriter) + { + Formatting = prettyPrint ? Formatting.Indented : Formatting.None, + StringEscapeHandling = StringEscapeHandling.EscapeHtml, + }; + + string comment = "comment is * / valid"; + json.WriteComment(comment); + json.WriteComment(comment); + json.WriteComment(comment); + + comment = "comment is /* valid"; + json.WriteComment(comment); + json.WriteComment(comment); + json.WriteComment(comment); + + comment = "comment is / * valid even with unpaired surrogate "; + json.WriteComment(comment); + json.WriteComment(comment); + + json.Flush(); + + return Encoding.UTF8.GetString(ms.ToArray()); + } + [Theory] [InlineData(true, true)] [InlineData(true, false)] @@ -1353,12 +1798,12 @@ public void WriteStrings(bool formatted, bool skipValidation) string value = "temp"; string expectedStr = GetStringsExpectedString(prettyPrint: formatted, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); @@ -1375,27 +1820,13 @@ public void WriteStrings(bool formatted, bool skipValidation) case 2: jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value)); break; - case 3: - jsonUtf8.WriteStringValue(value, escape: false); - break; - case 4: - jsonUtf8.WriteStringValue(value.AsSpan(), escape: false); - break; - case 5: - jsonUtf8.WriteStringValue(Encoding.UTF8.GetBytes(value), escape: false); - break; } } jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1423,87 +1854,51 @@ public void WriteStrings(bool formatted, bool skipValidation) public void WriteHelloWorldEscaped(bool formatted, bool skipValidation, string key, string value) { string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml); - string expectedStrNoEscape = GetEscapedExpectedString(prettyPrint: formatted, key, value, StringEscapeHandling.EscapeHtml, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 18; i++) + for (int i = 0; i < 9; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(key, value, escape: true); + jsonUtf8.WriteString(key, value); break; case 1: - jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: true); + jsonUtf8.WriteString(key.AsSpan(), value.AsSpan()); break; case 2: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value)); break; case 3: - jsonUtf8.WriteString(key, value.AsSpan(), escape: true); + jsonUtf8.WriteString(key, value.AsSpan()); break; case 4: - jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value)); break; case 5: - jsonUtf8.WriteString(key.AsSpan(), value, escape: true); + jsonUtf8.WriteString(key.AsSpan(), value); break; case 6: - jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: true); + jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value)); break; case 7: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: true); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value); break; case 8: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: true); - break; - case 9: - jsonUtf8.WriteString(key, value, escape: false); - break; - case 10: - jsonUtf8.WriteString(key.AsSpan(), value.AsSpan(), escape: false); - break; - case 11: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(value), escape: false); - break; - case 12: - jsonUtf8.WriteString(key, value.AsSpan(), escape: false); - break; - case 13: - jsonUtf8.WriteString(key, Encoding.UTF8.GetBytes(value), escape: false); - break; - case 14: - jsonUtf8.WriteString(key.AsSpan(), value, escape: false); - break; - case 15: - jsonUtf8.WriteString(key.AsSpan(), Encoding.UTF8.GetBytes(value), escape: false); - break; - case 16: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value, escape: false); - break; - case 17: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan(), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(key), value.AsSpan()); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i >= 9) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1534,41 +1929,28 @@ public void EscapeAsciiCharacters(bool formatted, bool skipValidation) string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeHtml, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1595,41 +1977,28 @@ public void EscapeCharacters(bool formatted, bool skipValidation) string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1647,41 +2016,28 @@ public void EscapeSurrogatePairs(bool formatted, bool skipValidation) string expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - for (int i = 0; i < 4; i++) + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + for (int i = 0; i < 2; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteString(propertyName, value, escape: true); + jsonUtf8.WriteString(propertyName, value); break; case 1: - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: true); - break; - case 2: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(propertyName, value, escape: false); - break; - case 3: - expectedStr = GetEscapedExpectedString(prettyPrint: formatted, propertyName, value, StringEscapeHandling.EscapeNonAscii, escape: false); - jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value), escape: false); + jsonUtf8.WriteString(Encoding.UTF8.GetBytes(propertyName), Encoding.UTF8.GetBytes(value)); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1692,58 +2048,73 @@ public void EscapeSurrogatePairs(bool formatted, bool skipValidation) [InlineData(false, false)] public void InvalidUTF8(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + var validUtf8 = new byte[2] { 0xc3, 0xb1 }; // 0xF1 + var invalidUtf8 = new byte[2] { 0xc3, 0x28 }; jsonUtf8.WriteStartObject(); - for (int i = 0; i < 8; i++) + for (int i = 0; i < 4; i++) { - try + switch (i) { - switch (i) - { - case 0: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: false); - AssertWriterThrow(noThrow: false); - break; - case 1: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: false); - AssertWriterThrow(noThrow: true); - break; - case 2: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: false); - AssertWriterThrow(noThrow: false); - break; - case 3: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: false); - AssertWriterThrow(noThrow: true); - break; - case 4: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0x28 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 5: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0x28 }, new byte[2] { 0xc3, 0xb1 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 6: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0x28 }, escape: true); - AssertWriterThrow(noThrow: false); - break; - case 7: - jsonUtf8.WriteString(new byte[2] { 0xc3, 0xb1 }, new byte[2] { 0xc3, 0xb1 }, escape: true); - AssertWriterThrow(noThrow: true); - break; - } + case 0: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf8, invalidUtf8)); + break; + case 1: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf8, validUtf8)); + break; + case 2: + Assert.Throws(() => jsonUtf8.WriteString(validUtf8, invalidUtf8)); + break; + case 3: + jsonUtf8.WriteString(validUtf8, validUtf8); + break; } - catch (ArgumentException) { } } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); + } - output.Dispose(); + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void InvalidUTF16(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); + + var validUtf16 = new char[2] { (char)0xD801, (char)0xDC37 }; // 0x10437 + var invalidUtf16 = new char[2] { (char)0xD801, 'a' }; + + jsonUtf8.WriteStartObject(); + for (int i = 0; i < 4; i++) + { + switch (i) + { + case 0: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf16, invalidUtf16)); + break; + case 1: + Assert.Throws(() => jsonUtf8.WriteString(invalidUtf16, validUtf16)); + break; + case 2: + Assert.Throws(() => jsonUtf8.WriteString(validUtf16, invalidUtf16)); + break; + case 3: + jsonUtf8.WriteString(validUtf16, validUtf16); + break; + } + } + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); } [Theory] @@ -1753,25 +2124,21 @@ public void InvalidUTF8(bool formatted, bool skipValidation) [InlineData(false, false)] public void WriteCustomStrings(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - var output = new ArrayBufferWriter(10); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(10); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); for (int i = 0; i < 1_000; i++) - jsonUtf8.WriteString("message", "Hello, World!", escape: false); + { + jsonUtf8.WriteString("message", "Hello, World!"); + } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(GetCustomExpectedString(formatted), actualStr); - - output.Dispose(); + AssertContents(GetCustomExpectedString(formatted), output); } [Theory] @@ -1783,11 +2150,10 @@ public void WriteStartEnd(bool formatted, bool skipValidation) { string expectedStr = GetStartEndExpectedString(prettyPrint: formatted); - var output = new ArrayBufferWriter(1024); - - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); jsonUtf8.WriteStartObject(); @@ -1795,12 +2161,7 @@ public void WriteStartEnd(bool formatted, bool skipValidation) jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } [Theory] @@ -1811,43 +2172,31 @@ public void WriteStartEndInvalid(bool formatted) { string expectedStr = "[}"; - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); - - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } { string expectedStr = "{]"; - var output = new ArrayBufferWriter(1024); - - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = true }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = true }; + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); jsonUtf8.WriteEndArray(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1860,34 +2209,25 @@ public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidati { string expectedStr = GetStartEndWithPropertyArrayExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray("property name", escape: false); + jsonUtf8.WriteStartArray("property name"); break; case 1: - jsonUtf8.WriteStartArray("property name".AsSpan(), escape: false); + jsonUtf8.WriteStartArray("property name".AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: false); - break; - case 3: - jsonUtf8.WriteStartArray("property name", escape: true); - break; - case 4: - jsonUtf8.WriteStartArray("property name".AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name"), escape: true); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("property name")); break; } @@ -1895,12 +2235,7 @@ public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidati jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1923,36 +2258,26 @@ public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidati var key = new string(keyChars); string expectedStr = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: true); - string expectedStrNoEscape = GetStartEndWithPropertyArrayExpectedString(key, prettyPrint: formatted, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray(key, escape: false); + jsonUtf8.WriteStartArray(key); break; case 1: - jsonUtf8.WriteStartArray(key.AsSpan(), escape: false); + jsonUtf8.WriteStartArray(key.AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: false); - break; - case 3: - jsonUtf8.WriteStartArray(key, escape: true); - break; - case 4: - jsonUtf8.WriteStartArray(key.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key), escape: true); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes(key)); break; } @@ -1960,15 +2285,7 @@ public void WriteStartEndWithPropertyNameArray(bool formatted, bool skipValidati jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -1981,34 +2298,25 @@ public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidat { string expectedStr = GetStartEndWithPropertyObjectExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartObject("property name", escape: false); + jsonUtf8.WriteStartObject("property name"); break; case 1: - jsonUtf8.WriteStartObject("property name".AsSpan(), escape: false); + jsonUtf8.WriteStartObject("property name".AsSpan()); break; case 2: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: false); - break; - case 3: - jsonUtf8.WriteStartObject("property name", escape: true); - break; - case 4: - jsonUtf8.WriteStartObject("property name".AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name"), escape: true); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes("property name")); break; } @@ -2016,12 +2324,7 @@ public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidat jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2044,52 +2347,34 @@ public void WriteStartEndWithPropertyNameObject(bool formatted, bool skipValidat var key = new string(keyChars); string expectedStr = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: true); - string expectedStrNoEscape = GetStartEndWithPropertyObjectExpectedString(key, prettyPrint: formatted, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartObject(key, escape: false); + jsonUtf8.WriteStartObject(key); break; case 1: - jsonUtf8.WriteStartObject(key.AsSpan(), escape: false); + jsonUtf8.WriteStartObject(key.AsSpan()); break; case 2: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: false); - break; - case 3: - jsonUtf8.WriteStartObject(key, escape: true); - break; - case 4: - jsonUtf8.WriteStartObject(key.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key), escape: true); + jsonUtf8.WriteStartObject(Encoding.UTF8.GetBytes(key)); break; } jsonUtf8.WriteEndObject(); - jsonUtf8.WriteEndObject(); - jsonUtf8.Flush(); - - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); + jsonUtf8.WriteEndObject(); + jsonUtf8.Flush(); - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2102,25 +2387,25 @@ public void WriteArrayWithProperty(bool formatted, bool skipValidation) { string expectedStr = GetArrayWithPropertyExpectedString(prettyPrint: formatted); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); + var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteStartArray("message", escape: false); + jsonUtf8.WriteStartArray("message"); break; case 1: - jsonUtf8.WriteStartArray("message".AsSpan(), escape: false); + jsonUtf8.WriteStartArray("message".AsSpan()); break; case 2: - jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message"), escape: false); + jsonUtf8.WriteStartArray(Encoding.UTF8.GetBytes("message")); break; } @@ -2128,12 +2413,7 @@ public void WriteArrayWithProperty(bool formatted, bool skipValidation) jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2165,36 +2445,26 @@ public void WriteArrayWithProperty(bool formatted, bool skipValidation) public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, string keyString) { string expectedStr = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: true); - string expectedStrNoEscape = GetBooleanExpectedString(prettyPrint: formatted, keyString, value, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteBoolean(keyString, value, escape: false); + jsonUtf8.WriteBoolean(keyString, value); break; case 1: - jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: false); + jsonUtf8.WriteBoolean(keyString.AsSpan(), value); break; case 2: - jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: false); - break; - case 3: - jsonUtf8.WriteBoolean(keyString, value, escape: true); - break; - case 4: - jsonUtf8.WriteBoolean(keyString.AsSpan(), value, escape: true); - break; - case 5: - jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value, escape: true); + jsonUtf8.WriteBoolean(Encoding.UTF8.GetBytes(keyString), value); break; } @@ -2208,15 +2478,7 @@ public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, s jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}, | Value: {value}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2236,36 +2498,29 @@ public void WriteBooleanValue(bool formatted, bool skipValidation, bool value, s public void WriteNullValue(bool formatted, bool skipValidation, string keyString) { string expectedStr = GetNullExpectedString(prettyPrint: formatted, keyString, escape: true); - string expectedStrNoEscape = GetNullExpectedString(prettyPrint: formatted, keyString, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(16); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteNull(keyString, escape: false); + jsonUtf8.WriteNull(keyString); + jsonUtf8.WriteNull(keyString); break; case 1: - jsonUtf8.WriteNull(keyString.AsSpan(), escape: false); + jsonUtf8.WriteNull(keyString.AsSpan()); + jsonUtf8.WriteNull(keyString.AsSpan()); break; case 2: - jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: false); - break; - case 3: - jsonUtf8.WriteNull(keyString, escape: true); - break; - case 4: - jsonUtf8.WriteNull(keyString.AsSpan(), escape: true); - break; - case 5: - jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString), escape: true); + jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString)); + jsonUtf8.WriteNull(Encoding.UTF8.GetBytes(keyString)); break; } @@ -2277,15 +2532,7 @@ public void WriteNullValue(bool formatted, bool skipValidation, string keyString jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.True(expectedStrNoEscape == actualStr, $"Case: {i}, | Expected: {expectedStrNoEscape}, | Actual: {actualStr}"); - else - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2318,37 +2565,32 @@ public void WriteIntegerValue(bool formatted, bool skipValidation, int value) { string expectedStr = GetIntegerExpectedString(prettyPrint: formatted, value); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); switch (i) { case 0: - jsonUtf8.WriteNumber("message", value, escape: false); + jsonUtf8.WriteNumber("message", value); break; case 1: - jsonUtf8.WriteNumber("message".AsSpan(), value, escape: false); + jsonUtf8.WriteNumber("message".AsSpan(), value); break; case 2: - jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value, escape: false); + jsonUtf8.WriteNumber(Encoding.UTF8.GetBytes("message"), value); break; } jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - Assert.True(expectedStr == actualStr, $"Case: {i}, | Expected: {expectedStr}, | Actual: {actualStr}, | Value: {value}"); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2493,14 +2735,13 @@ public void WriteNumbers(bool formatted, bool skipValidation, string keyString) } string expectedStr = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); - string expectedStrNoEscape = GetNumbersExpectedString(prettyPrint: formatted, keyString, ints, uints, longs, ulongs, floats, doubles, decimals, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; - for (int j = 0; j < 6; j++) + for (int j = 0; j < 3; j++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); @@ -2511,105 +2752,54 @@ public void WriteNumbers(bool formatted, bool skipValidation, string keyString) { case 0: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyString, floats[i], escape: false); + jsonUtf8.WriteNumber(keyString, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyString, ints[i], escape: false); + jsonUtf8.WriteNumber(keyString, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyString, uints[i], escape: false); + jsonUtf8.WriteNumber(keyString, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyString, doubles[i], escape: false); + jsonUtf8.WriteNumber(keyString, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyString, longs[i], escape: false); + jsonUtf8.WriteNumber(keyString, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyString, ulongs[i], escape: false); + jsonUtf8.WriteNumber(keyString, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyString, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteNumber(keyString, decimals[i]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: false); + jsonUtf8.WriteNumber(keyUtf16, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteNumber(keyUtf16, decimals[i]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: false); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: false); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: false); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: false); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: false); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: false); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyString, floats[i], escape: true); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyString, ints[i], escape: true); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyString, uints[i], escape: true); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyString, doubles[i], escape: true); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyString, longs[i], escape: true); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyString, ulongs[i], escape: true); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyString, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, floats[i], escape: true); - for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ints[i], escape: true); - for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, uints[i], escape: true); - for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, doubles[i], escape: true); - for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, longs[i], escape: true); - for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, ulongs[i], escape: true); - for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf16, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int i = 0; i < floats.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, floats[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, floats[i]); for (int i = 0; i < ints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ints[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, ints[i]); for (int i = 0; i < uints.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, uints[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, uints[i]); for (int i = 0; i < doubles.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, doubles[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, doubles[i]); for (int i = 0; i < longs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, longs[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, longs[i]); for (int i = 0; i < ulongs.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, ulongs[i], escape: true); + jsonUtf8.WriteNumber(keyUtf8, ulongs[i]); for (int i = 0; i < decimals.Length; i++) - jsonUtf8.WriteNumber(keyUtf8, decimals[i], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteNumber(keyUtf8, decimals[i]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2625,17 +2815,205 @@ public void WriteNumbers(bool formatted, bool skipValidation, string keyString) jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - // TODO: The output doesn't match what JSON.NET does (different rounding/e-notation). - //if (j < 3) - // Assert.Equal(expectedStrNoEscape, actualStr); - //else - // Assert.Equal(expectedStr, actualStr); + // AssertContents(expectedStr, output); + } + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueInt32(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue(1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueInt64(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((long)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueUInt32(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((uint)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueUInt64(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((ulong)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueSingle(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); - output.Dispose(); + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((float)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueDouble(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((double)1234567); + numberOfElements++; + } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); + } + + [Theory] + [InlineData(true, true)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(false, false)] + public void WriteNumberValueDecimal(bool formatted, bool skipValidation) + { + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + + var output = new ArrayBufferWriter(); + int initialCapacity = output.Capacity; + var jsonUtf8 = new Utf8JsonWriter(output, options); + + int numberOfElements = 0; + jsonUtf8.WriteStartArray(); + while (initialCapacity == output.Capacity) + { + jsonUtf8.WriteNumberValue((decimal)1234567); + numberOfElements++; } + Assert.Equal(initialCapacity + 4096, output.Capacity); + jsonUtf8.WriteEndArray(); + jsonUtf8.Flush(); + + string expectedStr = GetNumbersExpectedString(formatted, numberOfElements); + AssertContents(expectedStr, output); } [Theory] @@ -2657,20 +3035,21 @@ public void WriteGuidsValue(bool formatted, bool skipValidation, string keyStrin var guids = new Guid[numberOfItems]; for (int i = 0; i < numberOfItems; i++) + { guids[i] = Guid.NewGuid(); + } string expectedStr = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: true); - string expectedStrNoEscape = GetGuidsExpectedString(prettyPrint: formatted, keyString, guids, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2678,33 +3057,18 @@ public void WriteGuidsValue(bool formatted, bool skipValidation, string keyStrin { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, guids[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, guids[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, guids[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, guids[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, guids[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2715,15 +3079,7 @@ public void WriteGuidsValue(bool formatted, bool skipValidation, string keyStrin jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2753,17 +3109,16 @@ public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyS dates[i] = start.AddDays(random.Next(range)); string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2771,33 +3126,18 @@ public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyS { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, dates[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, dates[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, dates[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2808,15 +3148,7 @@ public void WriteDateTimesValue(bool formatted, bool skipValidation, string keyS jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2846,17 +3178,16 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin dates[i] = new DateTimeOffset(start.AddDays(random.Next(range))); string expectedStr = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: true); - string expectedStrNoEscape = GetDatesExpectedString(prettyPrint: formatted, keyString, dates, escape: false); - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; ReadOnlySpan keyUtf16 = keyString.AsSpan(); ReadOnlySpan keyUtf8 = Encoding.UTF8.GetBytes(keyString); - for (int i = 0; i < 6; i++) + for (int i = 0; i < 3; i++) { - var output = new ArrayBufferWriter(1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); @@ -2864,33 +3195,18 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin { case 0: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyString, escape: false); + jsonUtf8.WriteString(keyString, dates[j]); + jsonUtf8.WriteStartArray(keyString); break; case 1: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf16, escape: false); + jsonUtf8.WriteString(keyUtf16, dates[j]); + jsonUtf8.WriteStartArray(keyUtf16); break; case 2: for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: false); - jsonUtf8.WriteStartArray(keyUtf8, escape: false); - break; - case 3: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyString, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyString, escape: true); - break; - case 4: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf16, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf16, escape: true); - break; - case 5: - for (int j = 0; j < numberOfItems; j++) - jsonUtf8.WriteString(keyUtf8, dates[j], escape: true); - jsonUtf8.WriteStartArray(keyUtf8, escape: true); + jsonUtf8.WriteString(keyUtf8, dates[j]); + jsonUtf8.WriteStartArray(keyUtf8); break; } @@ -2901,15 +3217,7 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin jsonUtf8.WriteEndObject(); jsonUtf8.Flush(); - ArraySegment arraySegment = output.Formatted; - string actualStr = Encoding.UTF8.GetString(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - if (i < 3) - Assert.Equal(expectedStrNoEscape, actualStr); - else - Assert.Equal(expectedStr, actualStr); - - output.Dispose(); + AssertContents(expectedStr, output); } } @@ -2921,10 +3229,8 @@ public void WriteDateTimeOffsetsValue(bool formatted, bool skipValidation, strin [InlineData(false, false)] public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); - - Span key; - Span value; + byte[] key; + byte[] value; try { @@ -2936,30 +3242,25 @@ public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) return; } - key.Fill((byte)'a'); - value.Fill((byte)'b'); + key.AsSpan().Fill((byte)'a'); + value.AsSpan().Fill((byte)'b'); - var output = new ArrayBufferWriter(1024); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; + var output = new ArrayBufferWriter(1024); - try { - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); - jsonUtf8.WriteString(key, DateTime.Now, escape: false); - Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. KeyLength: {key.Length}"); + Assert.Throws(() => jsonUtf8.WriteString(key, DateTime.Now)); + Assert.Equal(0, output.WrittenCount); } - catch (ArgumentException) { } - try { - var jsonUtf8 = new Utf8JsonWriter(output, state); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartArray(); - jsonUtf8.WriteStringValue(value, escape: false); - Assert.True(false, $"Expected ArgumentException for data too large wasn't thrown. ValueLength: {value.Length}"); + Assert.Throws(() => jsonUtf8.WriteStringValue(value)); + Assert.Equal(0, output.WrittenCount); } - catch (ArgumentException) { } - - output.Dispose(); } [ConditionalTheory(nameof(IsX64))] @@ -2970,7 +3271,7 @@ public void WriteLargeKeyOrValue(bool formatted, bool skipValidation) [InlineData(false, false)] public void WriteLargeKeyValue(bool formatted, bool skipValidation) { - var state = new JsonWriterState(options: new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }); + var options = new JsonWriterOptions { Indented = formatted, SkipValidation = skipValidation }; Span key; Span value; @@ -2985,23 +3286,23 @@ public void WriteLargeKeyValue(bool formatted, bool skipValidation) return; } - WriteTooLargeHelper(state, key, value); - WriteTooLargeHelper(state, key.Slice(0, 1_000_000_000), value); - WriteTooLargeHelper(state, key, value.Slice(0, 1_000_000_000)); - WriteTooLargeHelper(state, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true); + WriteTooLargeHelper(options, key, value); + WriteTooLargeHelper(options, key.Slice(0, 1_000_000_000), value); + WriteTooLargeHelper(options, key, value.Slice(0, 1_000_000_000)); + WriteTooLargeHelper(options, key.Slice(0, 10_000_000 / 3), value.Slice(0, 10_000_000 / 3), noThrow: true); } - private static void WriteTooLargeHelper(JsonWriterState state, ReadOnlySpan key, ReadOnlySpan value, bool noThrow = false) + private static void WriteTooLargeHelper(JsonWriterOptions options, ReadOnlySpan key, ReadOnlySpan value, bool noThrow = false) { // Resizing is too slow, even for outerloop tests, so initialize to a large output size up front. - var output = new ArrayBufferWriter(noThrow ? 40_000_000 : 1024); - var jsonUtf8 = new Utf8JsonWriter(output, state); + var output = new ArrayBufferWriter(noThrow ? 40_000_000 : 1024); + var jsonUtf8 = new Utf8JsonWriter(output, options); jsonUtf8.WriteStartObject(); try { - jsonUtf8.WriteString(key, value, escape: false); + jsonUtf8.WriteString(key, value); if (!noThrow) { @@ -3018,47 +3319,24 @@ private static void WriteTooLargeHelper(JsonWriterState state, ReadOnlySpan buffer) + { + Assert.Equal( + expectedValue, + Encoding.UTF8.GetString( + buffer.WrittenSpan +#if netstandard + .ToArray() +#endif + )); + } } } diff --git a/src/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.csproj b/src/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.csproj index dd8fa5058853..db18da682aa3 100644 --- a/src/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.csproj +++ b/src/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.csproj @@ -1,7 +1,7 @@ {B262B15E-13E6-4C1E-A25E-16D06E222A09} - netcoreapp-Debug;netcoreapp-Release;netcoreappaot-Debug;netcoreappaot-Release;uap-Debug;uap-Release + netcoreapp-Debug;netcoreapp-Release;uap-Debug;uap-Release diff --git a/src/System.Text.RegularExpressions/src/Configurations.props b/src/System.Text.RegularExpressions/src/Configurations.props index e2dfc9e0f76c..c49f16c285fb 100644 --- a/src/System.Text.RegularExpressions/src/Configurations.props +++ b/src/System.Text.RegularExpressions/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp; - netcoreappaot; diff --git a/src/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj b/src/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj index de78bef33b6d..ca0a57cb69c2 100644 --- a/src/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj +++ b/src/System.Text.RegularExpressions/src/System.Text.RegularExpressions.csproj @@ -3,9 +3,9 @@ {2C58640B-5BED-4E83-9554-CD2B9762643F} System.Text.RegularExpressions $(DefineConstants);FEATURE_COMPILED - $(DefineConstants);FEATURE_COMPILEAPIS + $(DefineConstants);FEATURE_COMPILEAPIS true - netcoreapp-Debug;netcoreapp-Release;netcoreappaot-Debug;netcoreappaot-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Debug;netcoreapp-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release @@ -73,7 +73,7 @@ - + diff --git a/src/System.Threading.Overlapped/src/Configurations.props b/src/System.Threading.Overlapped/src/Configurations.props index 8f0029cd097c..1bb5366a0750 100644 --- a/src/System.Threading.Overlapped/src/Configurations.props +++ b/src/System.Threading.Overlapped/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading.Overlapped/src/System.Threading.Overlapped.csproj b/src/System.Threading.Overlapped/src/System.Threading.Overlapped.csproj index 82ce410cf78e..8cae8bd43f6e 100644 --- a/src/System.Threading.Overlapped/src/System.Threading.Overlapped.csproj +++ b/src/System.Threading.Overlapped/src/System.Threading.Overlapped.csproj @@ -5,7 +5,7 @@ true true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Threading.Tasks.Extensions/src/Configurations.props b/src/System.Threading.Tasks.Extensions/src/Configurations.props index 5136d110b51f..63d93e78c952 100644 --- a/src/System.Threading.Tasks.Extensions/src/Configurations.props +++ b/src/System.Threading.Tasks.Extensions/src/Configurations.props @@ -2,10 +2,9 @@ netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; uap-Windows_NT; + uapaot-Windows_NT; diff --git a/src/System.Threading.Tasks.Extensions/src/System.Threading.Tasks.Extensions.csproj b/src/System.Threading.Tasks.Extensions/src/System.Threading.Tasks.Extensions.csproj index 45be8240bca8..2771e416bb5f 100644 --- a/src/System.Threading.Tasks.Extensions/src/System.Threading.Tasks.Extensions.csproj +++ b/src/System.Threading.Tasks.Extensions/src/System.Threading.Tasks.Extensions.csproj @@ -2,7 +2,7 @@ {DE90AD0B-649D-4062-B8D9-9658DE140532} true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release diff --git a/src/System.Threading.Tasks/src/Configurations.props b/src/System.Threading.Tasks/src/Configurations.props index 06e87b8a189c..eb8b2ba06c34 100644 --- a/src/System.Threading.Tasks/src/Configurations.props +++ b/src/System.Threading.Tasks/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading.Tasks/src/System.Threading.Tasks.csproj b/src/System.Threading.Tasks/src/System.Threading.Tasks.csproj index 8e90d4ef3146..d0d9b2c0fc11 100644 --- a/src/System.Threading.Tasks/src/System.Threading.Tasks.csproj +++ b/src/System.Threading.Tasks/src/System.Threading.Tasks.csproj @@ -3,7 +3,7 @@ {3BCAEAA6-3A29-49EC-B334-6E7BE8BE9ABA} System.Threading.Tasks true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Threading.Thread/src/Configurations.props b/src/System.Threading.Thread/src/Configurations.props index 06e87b8a189c..eb8b2ba06c34 100644 --- a/src/System.Threading.Thread/src/Configurations.props +++ b/src/System.Threading.Thread/src/Configurations.props @@ -4,8 +4,6 @@ uapaot-Windows_NT; uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading.Thread/src/System.Threading.Thread.csproj b/src/System.Threading.Thread/src/System.Threading.Thread.csproj index 33d8d691df61..e8e088907edb 100644 --- a/src/System.Threading.Thread/src/System.Threading.Thread.csproj +++ b/src/System.Threading.Thread/src/System.Threading.Thread.csproj @@ -4,7 +4,7 @@ Library true {06197EED-FF48-43F3-976D-463839D43E8C} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Threading.ThreadPool/src/Configurations.props b/src/System.Threading.ThreadPool/src/Configurations.props index 8f0029cd097c..1bb5366a0750 100644 --- a/src/System.Threading.ThreadPool/src/Configurations.props +++ b/src/System.Threading.ThreadPool/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj b/src/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj index 6d3c277f62c6..ce47ce988477 100644 --- a/src/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj +++ b/src/System.Threading.ThreadPool/src/System.Threading.ThreadPool.csproj @@ -3,7 +3,7 @@ System.Threading.ThreadPool true {EE797598-BA64-4150-A3AA-8FB97DA63697} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Threading.Timer/src/Configurations.props b/src/System.Threading.Timer/src/Configurations.props index 8f0029cd097c..1bb5366a0750 100644 --- a/src/System.Threading.Timer/src/Configurations.props +++ b/src/System.Threading.Timer/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading.Timer/src/System.Threading.Timer.csproj b/src/System.Threading.Timer/src/System.Threading.Timer.csproj index d8d4d608b990..1fee5bf53980 100644 --- a/src/System.Threading.Timer/src/System.Threading.Timer.csproj +++ b/src/System.Threading.Timer/src/System.Threading.Timer.csproj @@ -3,7 +3,7 @@ System.Threading.Timer true {0F8B87B4-0E61-4DC6-9E90-CD4863025272} - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Threading/src/Configurations.props b/src/System.Threading/src/Configurations.props index 8f0029cd097c..1bb5366a0750 100644 --- a/src/System.Threading/src/Configurations.props +++ b/src/System.Threading/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Threading/src/System.Threading.csproj b/src/System.Threading/src/System.Threading.csproj index e343e41a1d3c..68797bd1bc7d 100644 --- a/src/System.Threading/src/System.Threading.csproj +++ b/src/System.Threading/src/System.Threading.csproj @@ -4,7 +4,7 @@ System.Threading true true - netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netcoreappaot-Windows_NT-Debug;netcoreappaot-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release + netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;uap-Windows_NT-Debug;uap-Windows_NT-Release;uapaot-Windows_NT-Debug;uapaot-Windows_NT-Release diff --git a/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj b/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj index 6563d2c44bdc..3066719d1149 100644 --- a/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj +++ b/src/System.Utf8String.Experimental/ref/System.Utf8String.Experimental.csproj @@ -4,7 +4,7 @@ {7AF57E6B-2CED-45C9-8BCA-5BBA60D018E0} netcoreapp-Debug;netcoreapp-Release - + diff --git a/src/System.Utf8String.Experimental/ref/System.Utf8String.cs b/src/System.Utf8String.Experimental/ref/System.Utf8String.cs index 7e9bc8f56e95..c3308621e2cb 100644 --- a/src/System.Utf8String.Experimental/ref/System.Utf8String.cs +++ b/src/System.Utf8String.Experimental/ref/System.Utf8String.cs @@ -84,9 +84,7 @@ public Utf8String(string value) { } public static implicit operator ReadOnlySpan(Utf8String value) => throw null; public static bool operator ==(Utf8String left, Utf8String right) => throw null; public static bool operator !=(Utf8String left, Utf8String right) => throw null; - public Char8 this[Index index] => throw null; public Char8 this[int index] => throw null; - public Utf8String this[Range range] => throw null; public int Length => throw null; public bool Contains(char value) => throw null; public bool Contains(System.Text.Rune value) => throw null; @@ -101,12 +99,12 @@ public Utf8String(string value) { } public int IndexOf(char value) => throw null; public int IndexOf(System.Text.Rune value) => throw null; public static bool IsNullOrEmpty(Utf8String value) => throw null; + [ComponentModel.EditorBrowsable(ComponentModel.EditorBrowsableState.Never)] // for compiler use only + public Utf8String Slice(int startIndex, int length) => throw null; public bool StartsWith(char value) => throw null; public bool StartsWith(System.Text.Rune value) => throw null; - public Utf8String Substring(Index startIndex) => throw null; public Utf8String Substring(int startIndex) => throw null; public Utf8String Substring(int startIndex, int length) => throw null; - public Utf8String Substring(Range range) => throw null; public byte[] ToByteArray() => throw null; public byte[] ToByteArray(int startIndex, int length) => throw null; public override string ToString() => throw null; diff --git a/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj b/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj index 964097b580ae..5c6b5eb174fa 100644 --- a/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj +++ b/src/System.Utf8String.Experimental/src/System.Utf8String.Experimental.csproj @@ -6,7 +6,7 @@ netcoreapp-Unix-Debug;netcoreapp-Unix-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release; System - + diff --git a/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj b/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj index 5ce4935e8ebf..83d935cc9e05 100644 --- a/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj +++ b/src/System.Utf8String.Experimental/tests/System.Utf8String.Experimental.Tests.csproj @@ -11,7 +11,7 @@ true - + diff --git a/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Substring.cs b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Substring.cs index b024dac6520e..f17047ca2cc6 100644 --- a/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Substring.cs +++ b/src/System.Utf8String.Experimental/tests/System/Utf8StringTests.Substring.cs @@ -29,16 +29,17 @@ public static void Substring_Index(string sAsString, int indexValue, bool fromEn void Substring_IndexCore(Utf8String s, Utf8String expected) { - Assert.Equal(expected, s.Substring(index)); + Range r = new Range(index, ^0); + Assert.Equal(expected, s[r]); if (index.Value == 0) { - Assert.Same(index.IsFromEnd ? Utf8String.Empty : s, s.Substring(index)); + Assert.Same(index.IsFromEnd ? Utf8String.Empty : s, s[r]); } if (index.Value == s.Length) { - Assert.Same(index.IsFromEnd ? s : Utf8String.Empty, s.Substring(index)); + Assert.Same(index.IsFromEnd ? s : Utf8String.Empty, s[r]); } }; @@ -88,18 +89,15 @@ public static void Substring_Range() { void Substring_RangeCore(Utf8String s, Range range, Utf8String expected) { - Assert.Equal(expected, s.Substring(range)); Assert.Equal(expected, s[range]); if (expected.Length == s.Length) { - Assert.Same(s, s.Substring(range)); Assert.Same(s, s[range]); } if (expected.Length == 0) { - Assert.Same(Utf8String.Empty, s.Substring(range)); Assert.Same(Utf8String.Empty, s[range]); } }; diff --git a/src/System.Windows.Extensions/src/System/Drawing/ImageConverter.cs b/src/System.Windows.Extensions/src/System/Drawing/ImageConverter.cs index b4e7bbf92118..38e8d8e03a3d 100644 --- a/src/System.Windows.Extensions/src/System/Drawing/ImageConverter.cs +++ b/src/System.Windows.Extensions/src/System/Drawing/ImageConverter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.ComponentModel; +using System.Drawing.Imaging; using System.Globalization; using System.IO; @@ -48,7 +49,17 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul { using (MemoryStream ms = new MemoryStream()) { - image.Save(ms, image.RawFormat); + ImageFormat dest = image.RawFormat; + // Jpeg loses data, so we don't want to use it to serialize. + if (dest == ImageFormat.Jpeg) + { + dest = ImageFormat.Png; + } + + // If we don't find an Encoder (for things like Icon), we + // just switch back to PNG. + ImageCodecInfo codec = FindEncoder(dest) ?? FindEncoder(ImageFormat.Png); + image.Save(ms, codec, null); return ms.ToArray(); } } @@ -57,6 +68,18 @@ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo cul throw GetConvertFromException(value); } + // Find any random encoder which supports this format. + private static ImageCodecInfo FindEncoder(ImageFormat imageformat) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders(); + foreach (ImageCodecInfo codec in codecs) + { + if (codec.FormatID.Equals(imageformat.Guid)) + return codec; + } + return null; + } + public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes) { return TypeDescriptor.GetProperties(typeof(Image), attributes); diff --git a/src/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj b/src/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj index 795e32746594..d2f1c5ee209a 100644 --- a/src/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj +++ b/src/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj @@ -2,7 +2,7 @@ {AC1A1515-70D8-42E4-9B19-A72F739E974C} netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release - 1.0.0 + 1.0.1 diff --git a/src/System.Windows.Extensions/tests/System/Drawing/ImageConverterTests.cs b/src/System.Windows.Extensions/tests/System/Drawing/ImageConverterTests.cs index 216ab93748bb..30761cd47a09 100644 --- a/src/System.Windows.Extensions/tests/System/Drawing/ImageConverterTests.cs +++ b/src/System.Windows.Extensions/tests/System/Drawing/ImageConverterTests.cs @@ -151,6 +151,15 @@ public void ConvertTo_ByteArray() Assert.Equal(_imageBytes, newImageBytes); } + [ConditionalFact(Helpers.IsDrawingSupported)] + public void ConvertTo_FromBitmapToByteArray() + { + Bitmap value = new Bitmap(64, 64); + ImageConverter converter = new ImageConverter(); + byte[] converted = (byte[])converter.ConvertTo(value, typeof(byte[])); + Assert.NotNull(converted); + } + [ConditionalFact(Helpers.IsDrawingSupported)] public void ConvertTo_ThrowsNotSupportedException() { diff --git a/src/System.Xml.ReaderWriter/src/Configurations.props b/src/System.Xml.ReaderWriter/src/Configurations.props index cca087cb0cb1..0a221e3e4710 100644 --- a/src/System.Xml.ReaderWriter/src/Configurations.props +++ b/src/System.Xml.ReaderWriter/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Xml.XDocument/src/Configurations.props b/src/System.Xml.XDocument/src/Configurations.props index cca087cb0cb1..0a221e3e4710 100644 --- a/src/System.Xml.XDocument/src/Configurations.props +++ b/src/System.Xml.XDocument/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Xml.XPath.XDocument/src/Configurations.props b/src/System.Xml.XPath.XDocument/src/Configurations.props index cca087cb0cb1..0a221e3e4710 100644 --- a/src/System.Xml.XPath.XDocument/src/Configurations.props +++ b/src/System.Xml.XPath.XDocument/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Xml.XPath.XDocument/src/System.Xml.XPath.XDocument.csproj b/src/System.Xml.XPath.XDocument/src/System.Xml.XPath.XDocument.csproj index 511f92052813..ba329a6ccc91 100644 --- a/src/System.Xml.XPath.XDocument/src/System.Xml.XPath.XDocument.csproj +++ b/src/System.Xml.XPath.XDocument/src/System.Xml.XPath.XDocument.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/System.Xml.XPath/src/Configurations.props b/src/System.Xml.XPath/src/Configurations.props index cca087cb0cb1..0a221e3e4710 100644 --- a/src/System.Xml.XPath/src/Configurations.props +++ b/src/System.Xml.XPath/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Xml.XmlDocument/src/Configurations.props b/src/System.Xml.XmlDocument/src/Configurations.props index cca087cb0cb1..0a221e3e4710 100644 --- a/src/System.Xml.XmlDocument/src/Configurations.props +++ b/src/System.Xml.XmlDocument/src/Configurations.props @@ -3,7 +3,6 @@ uap-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/System.Xml.XmlSerializer/src/Configurations.props b/src/System.Xml.XmlSerializer/src/Configurations.props index 8f0029cd097c..1bb5366a0750 100644 --- a/src/System.Xml.XmlSerializer/src/Configurations.props +++ b/src/System.Xml.XmlSerializer/src/Configurations.props @@ -4,8 +4,6 @@ uap-Windows_NT; uapaot-Windows_NT; netcoreapp-Windows_NT; - netcoreappaot-Windows_NT; - netcoreappaot-WebAssembly; netcoreapp-Unix; diff --git a/src/shims/ApiCompatBaseline.netcoreapp.netstandardOnly.txt b/src/shims/ApiCompatBaseline.netcoreapp.netstandardOnly.txt index 886ece61fb4e..b038da471ac3 100644 --- a/src/shims/ApiCompatBaseline.netcoreapp.netstandardOnly.txt +++ b/src/shims/ApiCompatBaseline.netcoreapp.netstandardOnly.txt @@ -58,4 +58,25 @@ TypesMustExist : Type 'System.Reflection.Emit.TypeToken' does not exist in the i CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Runtime.InteropServices.ManagedToNativeComInteropStubAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false, AllowMultiple=false)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlAnyAttributeAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlNamespaceDeclarationsAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. -Total Issues: 59 +MembersMustExist : Member 'System.Range.GetOffsetAndLength(System.Int32)' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Range.OffsetAndLength' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Item.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Item.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Chars.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Chars.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Substring(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Substring(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Utf8String.Slice(System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. +Total Issues: 80 diff --git a/src/shims/ApiCompatBaseline.netcoreappaot.netstandard.txt b/src/shims/ApiCompatBaseline.netcoreappaot.netstandard.txt deleted file mode 100644 index f9326ee8077a..000000000000 --- a/src/shims/ApiCompatBaseline.netcoreappaot.netstandard.txt +++ /dev/null @@ -1,29 +0,0 @@ -Compat issues with assembly mscorlib: -TypeCannotChangeClassification : Type 'System.RuntimeArgumentHandle' is a 'ref struct' in the implementation but is a 'struct' in the contract. -TypeCannotChangeClassification : Type 'System.TypedReference' is a 'ref struct' in the implementation but is a 'struct' in the contract. -Compat issues with assembly netstandard: -TypeCannotChangeClassification : Type 'System.RuntimeArgumentHandle' is a 'ref struct' in the implementation but is a 'struct' in the contract. -TypeCannotChangeClassification : Type 'System.TypedReference' is a 'ref struct' in the implementation but is a 'struct' in the contract. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.ComponentModel.BackgroundWorker' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultPropertyAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.IO.FileSystemWatcher' in the contract but not the implementation. -Compat issues with assembly System: -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.ComponentModel.BackgroundWorker' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultPropertyAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Event | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Struct, Inherited=false, AllowMultiple=false)]' in the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.IO.FileSystemWatcher' in the contract but not the implementation. -Compat issues with assembly System.ComponentModel.EventBasedAsync: -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.ComponentModel.BackgroundWorker' in the contract but not the implementation. -Compat issues with assembly System.Diagnostics.Process: -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultPropertyAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -Compat issues with assembly System.IO.FileSystem.Watcher: -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.IO.FileSystemWatcher' in the contract but not the implementation. -Compat issues with assembly System.Security.Cryptography.Pkcs: -MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.Certificates.set(System.Security.Cryptography.X509Certificates.X509Certificate2Collection)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.SignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.UnsignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. -Total Issues: 21 diff --git a/src/shims/ApiCompatBaseline.netcoreappaot.netstandardOnly.txt b/src/shims/ApiCompatBaseline.netcoreappaot.netstandardOnly.txt deleted file mode 100644 index c27f6f1b8971..000000000000 --- a/src/shims/ApiCompatBaseline.netcoreappaot.netstandardOnly.txt +++ /dev/null @@ -1,63 +0,0 @@ -Compat issues with assembly netstandard: -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.ComponentModel.BackgroundWorker' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Diagnostics.SwitchLevelAttribute' exists on 'System.Diagnostics.BooleanSwitch' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultPropertyAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.Diagnostics.SwitchLevelAttribute' exists on 'System.Diagnostics.TraceSwitch' in the contract but not the implementation. -CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.IO.FileSystemWatcher' in the contract but not the implementation. -MembersMustExist : Member 'System.Reflection.Emit.AssemblyBuilder.SetEntryPoint(System.Reflection.MethodInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ConstructorBuilder.GetModule()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ConstructorBuilder.GetToken()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ConstructorBuilder.SetMethodBody(System.Byte[], System.Int32, System.Byte[], System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ConstructorBuilder.Signature.get()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.DynamicILInfo' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.DynamicMethod.GetDynamicILInfo()' does not exist in the implementation but it does exist in the contract. -CannotRemoveBaseTypeOrInterface : Type 'System.Reflection.Emit.EnumBuilder' does not inherit from base type 'System.Reflection.TypeInfo' in the implementation but it does in the contract. -MembersMustExist : Member 'System.Reflection.Emit.EnumBuilder.CreateType()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.EnumBuilder.TypeToken.get()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.EventBuilder.GetEventToken()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.EventToken' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.ExceptionHandler' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.FieldBuilder.GetToken()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.FieldToken' does not exist in the implementation but it does exist in the contract. -CannotRemoveBaseTypeOrInterface : Type 'System.Reflection.Emit.GenericTypeParameterBuilder' does not inherit from base type 'System.Reflection.TypeInfo' in the implementation but it does in the contract. -CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Reflection.Emit.Label' in the contract but not the implementation. -CannotRemoveBaseTypeOrInterface : Type 'System.Reflection.Emit.Label' does not implement interface 'System.IEquatable' in the implementation but it does in the contract. -TypeCannotChangeClassification : Type 'System.Reflection.Emit.Label' is marked as readonly in the contract so it must also be marked readonly in the implementation. -MembersMustExist : Member 'System.Reflection.Emit.MethodBuilder.CreateMethodBody(System.Byte[], System.Int32)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.MethodBuilder.GetModule()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.MethodBuilder.GetToken()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.MethodBuilder.SetMethodBody(System.Byte[], System.Int32, System.Byte[], System.Collections.Generic.IEnumerable, System.Collections.Generic.IEnumerable)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.MethodBuilder.Signature.get()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.MethodToken' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetArrayMethodToken(System.Type, System.String, System.Reflection.CallingConventions, System.Type, System.Type[])' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetConstructorToken(System.Reflection.ConstructorInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetConstructorToken(System.Reflection.ConstructorInfo, System.Collections.Generic.IEnumerable)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetFieldToken(System.Reflection.FieldInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetMethodToken(System.Reflection.MethodInfo)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetMethodToken(System.Reflection.MethodInfo, System.Collections.Generic.IEnumerable)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetSignatureToken(System.Byte[], System.Int32)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetSignatureToken(System.Reflection.Emit.SignatureHelper)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetStringConstant(System.String)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetTypeToken(System.String)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.GetTypeToken(System.Type)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.IsTransient()' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.ModuleBuilder.SetUserEntryPoint(System.Reflection.MethodInfo)' does not exist in the implementation but it does exist in the contract. -CannotRemoveAttribute : Attribute 'System.Runtime.CompilerServices.IsReadOnlyAttribute' exists on 'System.Reflection.Emit.OpCode' in the contract but not the implementation. -CannotRemoveBaseTypeOrInterface : Type 'System.Reflection.Emit.OpCode' does not implement interface 'System.IEquatable' in the implementation but it does in the contract. -TypeCannotChangeClassification : Type 'System.Reflection.Emit.OpCode' is marked as readonly in the contract so it must also be marked readonly in the implementation. -MembersMustExist : Member 'System.Reflection.Emit.ParameterBuilder.GetToken()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.ParameterToken' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.PropertyBuilder.PropertyToken.get()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.PropertyToken' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(System.Reflection.Module, System.Runtime.InteropServices.CallingConvention, System.Type)' does not exist in the implementation but it does exist in the contract. -MembersMustExist : Member 'System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(System.Runtime.InteropServices.CallingConvention, System.Type)' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.SignatureToken' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.StringToken' does not exist in the implementation but it does exist in the contract. -CannotRemoveBaseTypeOrInterface : Type 'System.Reflection.Emit.TypeBuilder' does not inherit from base type 'System.Reflection.TypeInfo' in the implementation but it does in the contract. -MembersMustExist : Member 'System.Reflection.Emit.TypeBuilder.TypeToken.get()' does not exist in the implementation but it does exist in the contract. -TypesMustExist : Type 'System.Reflection.Emit.TypeToken' does not exist in the implementation but it does exist in the contract. -CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Runtime.InteropServices.ManagedToNativeComInteropStubAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false, AllowMultiple=false)]' in the implementation. -CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlAnyAttributeAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. -CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlNamespaceDeclarationsAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. -Total Issues: 61 diff --git a/src/shims/ApiCompatBaseline.netfx.netstandardOnly.txt b/src/shims/ApiCompatBaseline.netfx.netstandardOnly.txt index fea8156cb5f1..f68769e14f08 100644 --- a/src/shims/ApiCompatBaseline.netfx.netstandardOnly.txt +++ b/src/shims/ApiCompatBaseline.netfx.netstandardOnly.txt @@ -755,6 +755,8 @@ MembersMustExist : Member 'System.Security.Cryptography.IncrementalHash.AppendDa MembersMustExist : Member 'System.Security.Cryptography.IncrementalHash.TryGetHashAndReset(System.Span, System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.RandomNumberGenerator.Fill(System.Span)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.RandomNumberGenerator.GetBytes(System.Span)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Security.Cryptography.RandomNumberGenerator.GetInt32(System.Int32)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Security.Cryptography.RandomNumberGenerator.GetInt32(System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.RandomNumberGenerator.GetNonZeroBytes(System.Span)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.Rfc2898DeriveBytes.HashAlgorithm.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.RSA.TryDecrypt(System.ReadOnlySpan, System.Span, System.Security.Cryptography.RSAEncryptionPadding, System.Int32)' does not exist in the implementation but it does exist in the contract. @@ -865,4 +867,4 @@ MembersMustExist : Member 'System.Xml.Linq.XText.WriteToAsync(System.Xml.XmlWrit TypesMustExist : Type 'System.Xml.Serialization.SchemaImporter' does not exist in the implementation but it does exist in the contract. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlAnyAttributeAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlNamespaceDeclarationsAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. -Total Issues: 866 +Total Issues: 868 diff --git a/src/shims/ApiCompatBaseline.uap.netstandard.txt b/src/shims/ApiCompatBaseline.uap.netstandard.txt index fb4eaaf582db..9478e274be96 100644 --- a/src/shims/ApiCompatBaseline.uap.netstandard.txt +++ b/src/shims/ApiCompatBaseline.uap.netstandard.txt @@ -90,8 +90,13 @@ TypeCannotChangeClassification : Type 'System.Func' is a 'class' in the implementation but is a 'delegate' in the contract. Compat issues with assembly System.Runtime.Handles: TypeCannotChangeClassification : Type 'System.IO.HandleInheritability' is a 'class' in the implementation but is a 'struct' in the contract. +Compat issues with assembly System.Security.Cryptography.OpenSsl: +TypesMustExist : Type 'System.Security.Cryptography.DSAOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.ECDsaOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.RSAOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.SafeEvpPKeyHandle' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Security.Cryptography.Pkcs: MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.Certificates.set(System.Security.Cryptography.X509Certificates.X509Certificate2Collection)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.SignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.UnsignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. -Total Issues: 82 \ No newline at end of file +Total Issues: 86 \ No newline at end of file diff --git a/src/shims/ApiCompatBaseline.uap.netstandardOnly.txt b/src/shims/ApiCompatBaseline.uap.netstandardOnly.txt index bf4bfa721b8b..5d2d2879f593 100644 --- a/src/shims/ApiCompatBaseline.uap.netstandardOnly.txt +++ b/src/shims/ApiCompatBaseline.uap.netstandardOnly.txt @@ -61,4 +61,25 @@ TypesMustExist : Type 'System.Reflection.Emit.TypeToken' does not exist in the i CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Runtime.InteropServices.ManagedToNativeComInteropStubAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Method, Inherited=false, AllowMultiple=false)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlAnyAttributeAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. CannotChangeAttribute : Attribute 'System.AttributeUsageAttribute' on 'System.Xml.Serialization.XmlNamespaceDeclarationsAttribute' changed from '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue)]' in the contract to '[AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple=false)]' in the implementation. -Total Issues: 62 +MembersMustExist : Member 'System.Range.GetOffsetAndLength(System.Int32)' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Range.OffsetAndLength' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Memory.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlyMemory.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Item.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.ReadOnlySpan.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Item.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Item.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Slice(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Span.Slice(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Chars.get(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Chars.get(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Substring(System.Index)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.String.Substring(System.Range)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'System.Utf8String.Slice(System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract. +Total Issues: 83 diff --git a/src/shims/ApiCompatBaseline.uapaot.netstandard.txt b/src/shims/ApiCompatBaseline.uapaot.netstandard.txt index 133321f5527d..360298a12ef5 100644 --- a/src/shims/ApiCompatBaseline.uapaot.netstandard.txt +++ b/src/shims/ApiCompatBaseline.uapaot.netstandard.txt @@ -96,8 +96,13 @@ MembersMustExist : Member 'System.Runtime.CompilerServices.RuntimeHelpers.Prepar MembersMustExist : Member 'System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(System.RuntimeMethodHandle, System.RuntimeTypeHandle[])' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Runtime.Handles: TypeCannotChangeClassification : Type 'System.IO.HandleInheritability' is a 'class' in the implementation but is a 'struct' in the contract. +Compat issues with assembly System.Security.Cryptography.OpenSsl: +TypesMustExist : Type 'System.Security.Cryptography.DSAOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.ECDsaOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.RSAOpenSsl' does not exist in the implementation but it does exist in the contract. +TypesMustExist : Type 'System.Security.Cryptography.SafeEvpPKeyHandle' does not exist in the implementation but it does exist in the contract. Compat issues with assembly System.Security.Cryptography.Pkcs: MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.Certificates.set(System.Security.Cryptography.X509Certificates.X509Certificate2Collection)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.SignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'System.Security.Cryptography.Pkcs.CmsSigner.UnsignedAttributes.set(System.Security.Cryptography.CryptographicAttributeObjectCollection)' does not exist in the implementation but it does exist in the contract. -Total Issues: 88 +Total Issues: 92 \ No newline at end of file diff --git a/src/shims/ApiCompatBaseline.uapaot.netstandardOnly.txt b/src/shims/ApiCompatBaseline.uapaot.netstandardOnly.txt index 82ebc65bd651..dbe7dec89107 100644 --- a/src/shims/ApiCompatBaseline.uapaot.netstandardOnly.txt +++ b/src/shims/ApiCompatBaseline.uapaot.netstandardOnly.txt @@ -1,8 +1,9 @@ Compat issues with assembly netstandard: CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.ComponentModel.BackgroundWorker' in the contract but not the implementation. -CannotChangeAttribute : Attribute 'System.Diagnostics.SwitchLevelAttribute' on 'System.Diagnostics.BooleanSwitch' changed from '[SwitchLevelAttribute(typeof(bool))]' in the contract to '[SwitchLevelAttribute(typeof(Boolean))]' in the implementation. +CannotRemoveAttribute : Attribute 'System.Diagnostics.SwitchLevelAttribute' exists on 'System.Diagnostics.BooleanSwitch' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultPropertyAttribute' exists on 'System.Diagnostics.Process' in the contract but not the implementation. +CannotRemoveAttribute : Attribute 'System.Diagnostics.SwitchLevelAttribute' exists on 'System.Diagnostics.TraceSwitch' in the contract but not the implementation. CannotRemoveAttribute : Attribute 'System.ComponentModel.DefaultEventAttribute' exists on 'System.IO.FileSystemWatcher' in the contract but not the implementation. TypesMustExist : Type 'System.IO.Compression.BrotliDecoder' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'System.IO.Compression.BrotliEncoder' does not exist in the implementation but it does exist in the contract. diff --git a/src/shims/manual/Directory.Build.props b/src/shims/manual/Directory.Build.props index 3f8405a4262e..43c8dd91b7b9 100644 --- a/src/shims/manual/Directory.Build.props +++ b/src/shims/manual/Directory.Build.props @@ -2,8 +2,8 @@ netcoreapp; - netcoreappaot; uap; + uapaot; true true @@ -21,11 +21,15 @@ false $(NetFxRefPath)$(MSBuildProjectName).dll $(DefineConstants);netcoreapp + + <_runtimeOSGroup>$(_bc_OSGroup) + <_runtimeOSGroup Condition="'$(TargetsUAP)' == 'true'">Windows_NT + $(ArtifactsBinDir)runtime/$(TargetGroup)-$(_runtimeOSGroup)-$(_bc_ConfigurationGroup)-$(ArchGroup)/ + Include="$(RefPath)System.*.dll;$(RefPath)Microsoft.Win32.*.dll;$(RefPath)netstandard.dll" + Exclude="$(RefPath)System.dll;$(RefPath)System.Data.dll" /> diff --git a/src/shims/manual/mscorlib.csproj b/src/shims/manual/mscorlib.csproj index 19ed451f0e18..b2f9b8b276c7 100644 --- a/src/shims/manual/mscorlib.csproj +++ b/src/shims/manual/mscorlib.csproj @@ -4,36 +4,37 @@ Debug;Release - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/shims/manual/mscorlib.forwards.cs b/src/shims/manual/mscorlib.forwards.cs index e3f499236dec..67d99409b717 100644 --- a/src/shims/manual/mscorlib.forwards.cs +++ b/src/shims/manual/mscorlib.forwards.cs @@ -20,3 +20,5 @@ [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.OrdinalComparer))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.UnitySerializationHolder))] [assembly:System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Diagnostics.Contracts.ContractException))] +// System.void typeforward requires a special C# syntax that we choose to handle here. +[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(void))] diff --git a/src/tests.builds b/src/tests.builds index c680eccc8f6b..2cf6fd7b2575 100644 --- a/src/tests.builds +++ b/src/tests.builds @@ -35,15 +35,5 @@ - - - - -