From 7022bf409eb990ed797fc73ded18279d38e9b725 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 16 Apr 2023 21:25:07 -0400 Subject: [PATCH] Add public Encoding.TryGetBytes/Chars --- .../System/Formats/Asn1/AsnDecoder.Text.cs | 32 +++++++++-------- .../tests/Reader/ReadUTF8String.cs | 3 -- .../src/System/Text/ASCIIEncoding.cs | 35 +++++++++++++------ .../src/System/Text/Encoding.Internal.cs | 26 +++++++++----- .../src/System/Text/Encoding.cs | 21 +++++++++-- .../src/System/Text/Latin1Encoding.cs | 26 ++++++++++---- .../src/System/Text/UTF8Encoding.Sealed.cs | 4 +-- .../src/System/Text/UTF8Encoding.cs | 35 +++++++++++++------ .../src/System/Xml/ValueHandle.cs | 3 +- .../System.Runtime/ref/System.Runtime.cs | 2 ++ .../ref/System.Text.Encoding.Extensions.cs | 4 +++ .../tests/EncodingTestHelpers.cs | 12 +++++++ .../tests/NegativeEncodingTests.cs | 15 ++++++++ 13 files changed, 157 insertions(+), 61 deletions(-) diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Text.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Text.cs index fb39a1049908..b9556fdd2850 100644 --- a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Text.cs +++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnDecoder.Text.cs @@ -390,16 +390,19 @@ public static partial class AsnDecoder Text.Encoding encoding, out int charsWritten) { - if (source.Length == 0) + try { - charsWritten = 0; - return true; - } +#if NET8_0_OR_GREATER + return encoding.TryGetChars(source, destination, out charsWritten); +#else + if (source.Length == 0) + { + charsWritten = 0; + return true; + } - fixed (byte* bytePtr = &MemoryMarshal.GetReference(source)) - fixed (char* charPtr = &MemoryMarshal.GetReference(destination)) - { - try + fixed (byte* bytePtr = &MemoryMarshal.GetReference(source)) + fixed (char* charPtr = &MemoryMarshal.GetReference(destination)) { int charCount = encoding.GetCharCount(bytePtr, source.Length); @@ -411,13 +414,14 @@ public static partial class AsnDecoder charsWritten = encoding.GetChars(bytePtr, source.Length, charPtr, destination.Length); Debug.Assert(charCount == charsWritten); - } - catch (DecoderFallbackException e) - { - throw new AsnContentException(SR.ContentException_DefaultMessage, e); - } - return true; + return true; + } +#endif + } + catch (DecoderFallbackException e) + { + throw new AsnContentException(SR.ContentException_DefaultMessage, e); } } diff --git a/src/libraries/System.Formats.Asn1/tests/Reader/ReadUTF8String.cs b/src/libraries/System.Formats.Asn1/tests/Reader/ReadUTF8String.cs index db38b0b2d4f3..23ebf28c4028 100644 --- a/src/libraries/System.Formats.Asn1/tests/Reader/ReadUTF8String.cs +++ b/src/libraries/System.Formats.Asn1/tests/Reader/ReadUTF8String.cs @@ -148,8 +148,6 @@ public sealed class ReadUTF8String if (output.Length > 0) { - output[0] = 'a'; - copied = reader.TryReadCharacterString( output.AsSpan(0, expectedValue.Length - 1), UniversalTagNumber.UTF8String, @@ -157,7 +155,6 @@ public sealed class ReadUTF8String Assert.False(copied, "reader.TryCopyUTF8String - too short"); Assert.Equal(0, charsWritten); - Assert.Equal('a', output[0]); } copied = reader.TryReadCharacterString( diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIEncoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIEncoding.cs index 57b345043e92..4d02033b642b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIEncoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/ASCIIEncoding.cs @@ -322,13 +322,8 @@ public override unsafe int GetBytes(ReadOnlySpan chars, Span bytes) } } - // TODO https://github.com/dotnet/runtime/issues/84425: Make this public. - /// Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough. - /// The span containing the set of characters to encode. - /// The byte span to hold the encoded bytes. - /// Upon successful completion of the operation, the number of bytes encoded into . - /// if all of the characters were encoded into the destination; if the destination was too small to contain all the encoded bytes. - internal override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) + /// + public override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) { fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) @@ -618,8 +613,26 @@ public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) } } + /// + public override unsafe bool TryGetChars(ReadOnlySpan bytes, Span chars, out int charsWritten) + { + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + { + int written = GetCharsCommon(bytesPtr, bytes.Length, charsPtr, chars.Length, throwForDestinationOverflow: false); + if (written >= 0) + { + charsWritten = written; + return true; + } + + charsWritten = 0; + return false; + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int charCount) + private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int charCount, bool throwForDestinationOverflow = true) { // 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. @@ -643,7 +656,7 @@ private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int { // Simple narrowing conversion couldn't operate on entire buffer - invoke fallback. - return GetCharsWithFallback(pBytes, byteCount, pChars, charCount, bytesConsumed, charsWritten); + return GetCharsWithFallback(pBytes, byteCount, pChars, charCount, bytesConsumed, charsWritten, throwForDestinationOverflow); } } @@ -654,7 +667,7 @@ private protected sealed override unsafe int GetCharsFast(byte* pBytes, int byte return bytesConsumed; } - private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder) + private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder, bool throwForDestinationOverflow = true) { // We special-case DecoderReplacementFallback if it's telling us to write a single BMP char, // since we believe this to be relatively common and we can handle it more efficiently than @@ -699,7 +712,7 @@ private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan [MethodImpl(MethodImplOptions.NoInlining)] - private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int originalByteCount, char* pOriginalChars, int originalCharCount, int bytesConsumedSoFar, int charsWrittenSoFar) + private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int originalByteCount, char* pOriginalChars, int originalCharCount, int bytesConsumedSoFar, int charsWrittenSoFar, bool throwForDestinationOverflow = true) { // This is a stub method that's marked "no-inlining" so that it we don't stack-spill spans // into our immediate caller. Doing so increases the method prolog in what's supposed to @@ -1095,7 +1095,8 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig originalBytesLength: originalByteCount, chars: new Span(pOriginalChars, originalCharCount).Slice(charsWrittenSoFar), originalCharsLength: originalCharCount, - decoder: null); + decoder: null, + throwForDestinationOverflow); } /// @@ -1104,7 +1105,7 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig /// and signal where in the provided buffers the fallback loop /// should begin operating. The behavior of this method is to drain any leftover data in the /// instance, then to invoke the virtual method - /// after data has been drained, then to call . + /// after data has been drained, then to call GetCharsWithFallback(ReadOnlySpan{byte}, int, Span{char}, int, DecoderNLS, out bool)/>. /// /// /// The total number of chars written to , including . @@ -1183,7 +1184,7 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig /// implementation, deferring to the base implementation if needed. This method calls /// if necessary. /// - private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder) + private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder, bool throwForDestinationOverflow = true) { Debug.Assert(!bytes.IsEmpty, "Caller shouldn't invoke this method with an empty input buffer."); Debug.Assert(originalBytesLength >= 0, "Caller provided invalid parameter."); @@ -1272,11 +1273,18 @@ private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan byt if (!bytes.IsEmpty) { - // The line below will also throw if the decoder couldn't make any progress at all - // because the output buffer wasn't large enough to contain the result of even - // a single scalar conversion or fallback. - - ThrowCharsOverflow(decoder, nothingDecoded: chars.Length == originalCharsLength); + if (throwForDestinationOverflow) + { + // The line below will also throw if the decoder couldn't make any progress at all + // because the output buffer wasn't large enough to contain the result of even + // a single scalar conversion or fallback. + ThrowCharsOverflow(decoder, nothingDecoded: chars.Length == originalCharsLength); + } + else + { + Debug.Assert(decoder is null); + return -1; + } } // If a DecoderNLS instance is active, update its "total consumed byte count" value. diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs index 5ebbeeafcfad..9935024ae8fc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs @@ -733,13 +733,12 @@ public virtual unsafe int GetBytes(ReadOnlySpan chars, Span bytes) } } - // TODO https://github.com/dotnet/runtime/issues/84425: Make this public. /// Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough. /// The span containing the set of characters to encode. /// The byte span to hold the encoded bytes. /// Upon successful completion of the operation, the number of bytes encoded into . /// if all of the characters were encoded into the destination; if the destination was too small to contain all the encoded bytes. - internal virtual bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) + public virtual bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) { int required = GetByteCount(chars); if (required <= bytes.Length) @@ -883,6 +882,24 @@ public virtual unsafe int GetChars(ReadOnlySpan bytes, Span chars) } } + /// Decodes into a span of chars a set of bytes from the specified read-only span if the destination is large enough. + /// A read-only span containing the sequence of bytes to decode. + /// The character span receiving the decoded bytes. + /// Upon successful completion of the operation, the number of chars decoded into . + /// if all of the characters were decoded into the destination; if the destination was too small to contain all the decoded chars. + public virtual bool TryGetChars(ReadOnlySpan bytes, Span chars, out int charsWritten) + { + int required = GetCharCount(bytes); + if (required <= chars.Length) + { + charsWritten = GetChars(bytes, chars); + return true; + } + + charsWritten = 0; + return false; + } + [CLSCompliant(false)] public unsafe string GetString(byte* bytes, int byteCount) { diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/Latin1Encoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/Latin1Encoding.cs index 7167d4de6d6d..04d0e4f93e09 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/Latin1Encoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/Latin1Encoding.cs @@ -234,13 +234,8 @@ public override unsafe int GetBytes(ReadOnlySpan chars, Span bytes) } } - // TODO https://github.com/dotnet/runtime/issues/84425: Make this public. - /// Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough. - /// The span containing the set of characters to encode. - /// The byte span to hold the encoded bytes. - /// Upon successful completion of the operation, the number of bytes encoded into . - /// if all of the characters were encoded into the destination; if the destination was too small to contain all the encoded bytes. - internal override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) + /// + public override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) { fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) @@ -534,6 +529,23 @@ public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) } } + /// + public override unsafe bool TryGetChars(ReadOnlySpan bytes, Span chars, out int charsWritten) + { + if (bytes.Length <= chars.Length) + { + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + { + charsWritten = GetCharsCommon(bytesPtr, bytes.Length, charsPtr, chars.Length); + return true; + } + } + + charsWritten = 0; + return false; + } + public override unsafe string GetString(byte[] bytes) { if (bytes is null) diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.Sealed.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.Sealed.cs index 3679e90e34d5..dc5f36deca8d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.Sealed.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.Sealed.cs @@ -147,9 +147,9 @@ private unsafe string GetStringForSmallInput(byte[] bytes) return new string(new ReadOnlySpan(ref *pDestination, charsWritten)); // this overload of ROS ctor doesn't validate length } - // TODO https://github.com/dotnet/runtime/issues/84425: Make this public. // TODO: Make this [Intrinsic] and handle JIT-time UTF8 encoding of literal `chars`. - internal override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) + /// + public override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) { return base.TryGetBytes(chars, bytes, out bytesWritten); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.cs b/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.cs index 9bb500f5ddf0..f4eb8a5358a2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/UTF8Encoding.cs @@ -369,13 +369,8 @@ public override unsafe int GetBytes(ReadOnlySpan chars, Span bytes) } } - // TODO https://github.com/dotnet/runtime/issues/84425: Make this public. - /// Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough. - /// The span containing the set of characters to encode. - /// The byte span to hold the encoded bytes. - /// Upon successful completion of the operation, the number of bytes encoded into . - /// if all of the characters were encoded into the destination; if the destination was too small to contain all the encoded bytes. - internal override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) + /// + public override unsafe bool TryGetBytes(ReadOnlySpan chars, Span bytes, out int bytesWritten) { fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) @@ -569,6 +564,24 @@ public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) } } + /// + public override unsafe bool TryGetChars(ReadOnlySpan bytes, Span chars, out int charsWritten) + { + fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes)) + fixed (char* charsPtr = &MemoryMarshal.GetReference(chars)) + { + int written = GetCharsCommon(bytesPtr, bytes.Length, charsPtr, chars.Length, throwForDestinationOverflow: false); + if (written >= 0) + { + charsWritten = written; + return true; + } + + charsWritten = 0; + return false; + } + } + // 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 @@ -577,7 +590,7 @@ public override unsafe int GetChars(ReadOnlySpan bytes, Span chars) // 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) + private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int charCount, bool throwForDestinationOverflow = true) { // 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. @@ -601,7 +614,7 @@ private unsafe int GetCharsCommon(byte* pBytes, int byteCount, char* pChars, int { // Simple narrowing conversion couldn't operate on entire buffer - invoke fallback. - return GetCharsWithFallback(pBytes, byteCount, pChars, charCount, bytesConsumed, charsWritten); + return GetCharsWithFallback(pBytes, byteCount, pChars, charCount, bytesConsumed, charsWritten, throwForDestinationOverflow); } } @@ -618,7 +631,7 @@ private protected sealed override unsafe int GetCharsFast(byte* pBytes, int byte return (int)(pOutputBufferRemaining - pChars); } - private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder) + private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan bytes, int originalBytesLength, Span chars, int originalCharsLength, DecoderNLS? decoder, bool throwForDestinationOverflow = true) { // 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 @@ -649,7 +662,7 @@ private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan= encoding.GetMaxCharCount(byteCount) || charCount >= encoding.GetCharCount(bytes, byteOffset, byteCount)) + if (encoding.TryGetChars(bytes.AsSpan(byteOffset, byteCount), chars.AsSpan(charOffset, charCount), out actualCharCount)) { - actualCharCount = encoding.GetChars(bytes, byteOffset, byteCount, chars, charOffset); actualByteCount = byteCount; } else diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index f6103152f09b..3090e6223d88 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -14433,6 +14433,8 @@ public abstract partial class Encoding : System.ICloneable public bool IsAlwaysNormalized() { throw null; } public virtual bool IsAlwaysNormalized(System.Text.NormalizationForm form) { throw null; } public static void RegisterProvider(System.Text.EncodingProvider provider) { } + public virtual bool TryGetBytes(System.ReadOnlySpan chars, System.Span bytes, out int bytesWritten) { throw null; } + public virtual bool TryGetChars(System.ReadOnlySpan bytes, System.Span chars, out int charsWritten) { throw null; } } public sealed partial class EncodingInfo { diff --git a/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs b/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs index a13aedd4f573..0c9b8230911f 100644 --- a/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs +++ b/src/libraries/System.Text.Encoding.Extensions/ref/System.Text.Encoding.Extensions.cs @@ -33,6 +33,8 @@ public partial class ASCIIEncoding : System.Text.Encoding public override int GetMaxByteCount(int charCount) { throw null; } public override int GetMaxCharCount(int byteCount) { throw null; } public override string GetString(byte[] bytes, int byteIndex, int byteCount) { throw null; } + public override bool TryGetBytes(System.ReadOnlySpan chars, System.Span bytes, out int bytesWritten) { throw null; } + public override bool TryGetChars(System.ReadOnlySpan bytes, System.Span chars, out int charsWritten) { throw null; } } public partial class UnicodeEncoding : System.Text.Encoding { @@ -153,5 +155,7 @@ public partial class UTF8Encoding : System.Text.Encoding public override int GetMaxCharCount(int byteCount) { throw null; } public override byte[] GetPreamble() { throw null; } public override string GetString(byte[] bytes, int index, int count) { throw null; } + public override bool TryGetBytes(System.ReadOnlySpan chars, System.Span bytes, out int bytesWritten) { throw null; } + public override bool TryGetChars(System.ReadOnlySpan bytes, System.Span chars, out int charsWritten) { throw null; } } } diff --git a/src/libraries/System.Text.Encoding/tests/EncodingTestHelpers.cs b/src/libraries/System.Text.Encoding/tests/EncodingTestHelpers.cs index f049afdf196f..9b9170cf5826 100644 --- a/src/libraries/System.Text.Encoding/tests/EncodingTestHelpers.cs +++ b/src/libraries/System.Text.Encoding/tests/EncodingTestHelpers.cs @@ -268,6 +268,12 @@ static void GetBytes_NetCoreApp(Encoding encoding, string chars, int index, int Assert.Equal(expected.Length, encoding.GetBytes(chars.AsSpan(index, count), (Span)stringResultAdvanced)); VerifyGetBytes(stringResultAdvanced, 0, stringResultAdvanced.Length, new byte[expected.Length], expected); + // Use TryGetBytes(ReadOnlySpan, Span, out int bytesWritten) + Array.Clear(stringResultAdvanced); + Assert.True(encoding.TryGetBytes(chars.AsSpan(index, count), (Span)stringResultAdvanced, out int bytesWritten)); + Assert.Equal(expected.Length, bytesWritten); + VerifyGetBytes(stringResultAdvanced, 0, stringResultAdvanced.Length, new byte[expected.Length], expected); + if (count == 0) Assert.Equal(expected.Length, encoding.GetBytes(ReadOnlySpan.Empty, (Span)stringResultAdvanced)); } @@ -289,6 +295,12 @@ static void VerifyGetChars_NetCoreApp(Encoding encoding, byte[] bytes, int byteI VerifyGetChars(byteChars, charIndex, charCount, (char[])chars.Clone(), expectedChars); Assert.Equal(expectedChars.Length, charCount); + // Use TryGetChars(ReadOnlySpan, Span, out int charsWritten) + byteChars = (char[])chars.Clone(); + Assert.True(encoding.TryGetChars(new ReadOnlySpan(bytes, byteIndex, byteCount), new Span(byteChars).Slice(charIndex), out charCount)); + VerifyGetChars(byteChars, charIndex, charCount, (char[])chars.Clone(), expectedChars); + Assert.Equal(expectedChars.Length, charCount); + if (byteCount == 0) { charCount = encoding.GetChars(ReadOnlySpan.Empty, new Span(byteChars).Slice(charIndex)); diff --git a/src/libraries/System.Text.Encoding/tests/NegativeEncodingTests.cs b/src/libraries/System.Text.Encoding/tests/NegativeEncodingTests.cs index 1754d8771ab4..d0e1e5a51cfa 100644 --- a/src/libraries/System.Text.Encoding/tests/NegativeEncodingTests.cs +++ b/src/libraries/System.Text.Encoding/tests/NegativeEncodingTests.cs @@ -128,10 +128,22 @@ public static unsafe void GetBytes_Invalid(Encoding encoding) AssertExtensions.Throws("bytes", () => encoding.GetBytes("a", 0, 1, new byte[0], 0)); AssertExtensions.Throws("bytes", () => encoding.GetBytes("abc", 0, 3, new byte[1], 0)); AssertExtensions.Throws("bytes", () => encoding.GetBytes("\uD800\uDC00", 0, 2, new byte[1], 0)); + AssertExtensions.Throws("bytes", () => encoding.GetBytes(new char[1], 0, 1, new byte[0], 0)); AssertExtensions.Throws("bytes", () => encoding.GetBytes(new char[3], 0, 3, new byte[1], 0)); AssertExtensions.Throws("bytes", () => encoding.GetBytes("\uD800\uDC00".ToCharArray(), 0, 2, new byte[1], 0)); + AssertExtensions.Throws("bytes", () => encoding.GetBytes((ReadOnlySpan)new char[1], (Span)new byte[0])); + AssertExtensions.Throws("bytes", () => encoding.GetBytes((ReadOnlySpan)new char[3], (Span)new byte[1])); + AssertExtensions.Throws("bytes", () => encoding.GetBytes((ReadOnlySpan)"\uD800\uDC00".ToCharArray(), (Span)new byte[1])); + + Assert.False(encoding.TryGetBytes((ReadOnlySpan)new char[1], (Span)new byte[0], out int bytesWritten)); + Assert.Equal(0, bytesWritten); + Assert.False(encoding.TryGetBytes((ReadOnlySpan)new char[3], (Span)new byte[1], out bytesWritten)); + Assert.Equal(0, bytesWritten); + Assert.False(encoding.TryGetBytes((ReadOnlySpan)"\uD800\uDC00".ToCharArray(), (Span)new byte[1], out bytesWritten)); + Assert.Equal(0, bytesWritten); + char[] chars = new char[3]; byte[] bytes = new byte[3]; byte[] smallBytes = new byte[1]; @@ -223,6 +235,9 @@ public static unsafe void GetChars_Invalid(Encoding encoding) // Chars does not have enough capacity to accommodate result AssertExtensions.Throws("chars", () => encoding.GetChars(new byte[4], 0, 4, new char[1], 1)); + AssertExtensions.Throws("chars", () => encoding.GetChars((ReadOnlySpan)new byte[4], (new char[1]).AsSpan(1))); + Assert.False(encoding.TryGetChars((ReadOnlySpan)new byte[4], (new char[1]).AsSpan(1), out int charsWritten)); + Assert.Equal(0, charsWritten); byte[] bytes = new byte[encoding.GetMaxByteCount(2)]; char[] chars = new char[4];