Skip to content

Commit

Permalink
Add public Encoding.TryGetBytes/Chars (#85120)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephentoub authored Apr 23, 2023
1 parent e0e1085 commit a4ae184
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -390,16 +390,19 @@ private static unsafe bool TryReadCharacterStringCore(
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);

Expand All @@ -411,13 +414,14 @@ private static unsafe bool TryReadCharacterStringCore(

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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,13 @@ public static void TryCopyUTF8String(

if (output.Length > 0)
{
output[0] = 'a';

copied = reader.TryReadCharacterString(
output.AsSpan(0, expectedValue.Length - 1),
UniversalTagNumber.UTF8String,
out charsWritten);

Assert.False(copied, "reader.TryCopyUTF8String - too short");
Assert.Equal(0, charsWritten);
Assert.Equal('a', output[0]);
}

copied = reader.TryReadCharacterString(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,8 @@ public override unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes)
}
}

// TODO https://github.com/dotnet/runtime/issues/84425: Make this public.
/// <summary>Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough.</summary>
/// <param name="chars">The span containing the set of characters to encode.</param>
/// <param name="bytes">The byte span to hold the encoded bytes.</param>
/// <param name="bytesWritten">Upon successful completion of the operation, the number of bytes encoded into <paramref name="bytes"/>.</param>
/// <returns><see langword="true"/> if all of the characters were encoded into the destination; <see langword="false"/> if the destination was too small to contain all the encoded bytes.</returns>
internal override unsafe bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
/// <inheritdoc/>
public override unsafe bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
{
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
Expand Down Expand Up @@ -618,8 +613,26 @@ public override unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars)
}
}

/// <inheritdoc/>
public override unsafe bool TryGetChars(ReadOnlySpan<byte> bytes, Span<char> 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.
Expand All @@ -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);
}
}

Expand All @@ -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<byte> bytes, int originalBytesLength, Span<char> chars, int originalCharsLength, DecoderNLS? decoder)
private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan<byte> bytes, int originalBytesLength, Span<char> 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
Expand Down Expand Up @@ -699,7 +712,7 @@ private protected sealed override unsafe int GetCharsWithFallback(ReadOnlySpan<b
}
else
{
return base.GetCharsWithFallback(bytes, originalBytesLength, chars, originalCharsLength, decoder);
return base.GetCharsWithFallback(bytes, originalBytesLength, chars, originalCharsLength, decoder, throwForDestinationOverflow);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1081,7 +1081,7 @@ private protected virtual unsafe int GetCharsFast(byte* pBytes, int bytesLength,
/// If the destination buffer is not large enough to hold the entirety of the transcoded data.
/// </exception>
[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
Expand All @@ -1095,7 +1095,8 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig
originalBytesLength: originalByteCount,
chars: new Span<char>(pOriginalChars, originalCharCount).Slice(charsWrittenSoFar),
originalCharsLength: originalCharCount,
decoder: null);
decoder: null,
throwForDestinationOverflow);
}

/// <summary>
Expand All @@ -1104,7 +1105,7 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig
/// and <paramref name="charsWrittenSoFar"/> 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
/// <see cref="DecoderNLS"/> instance, then to invoke the <see cref="GetCharsFast"/> virtual method
/// after data has been drained, then to call <see cref="GetCharsWithFallback(ReadOnlySpan{byte}, int, Span{char}, int, DecoderNLS)"/>.
/// after data has been drained, then to call GetCharsWithFallback(ReadOnlySpan{byte}, int, Span{char}, int, DecoderNLS, out bool)/>.
/// </summary>
/// <returns>
/// The total number of chars written to <paramref name="pOriginalChars"/>, including <paramref name="charsWrittenSoFar"/>.
Expand Down Expand Up @@ -1183,7 +1184,7 @@ private protected unsafe int GetCharsWithFallback(byte* pOriginalBytes, int orig
/// implementation, deferring to the base implementation if needed. This method calls <see cref="ThrowCharsOverflow"/>
/// if necessary.
/// </remarks>
private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan<byte> bytes, int originalBytesLength, Span<char> chars, int originalCharsLength, DecoderNLS? decoder)
private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan<byte> bytes, int originalBytesLength, Span<char> 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.");
Expand Down Expand Up @@ -1272,11 +1273,18 @@ private protected virtual unsafe int GetCharsWithFallback(ReadOnlySpan<byte> 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.
Expand Down
21 changes: 19 additions & 2 deletions src/libraries/System.Private.CoreLib/src/System/Text/Encoding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -733,13 +733,12 @@ public virtual unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes)
}
}

// TODO https://github.com/dotnet/runtime/issues/84425: Make this public.
/// <summary>Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough.</summary>
/// <param name="chars">The span containing the set of characters to encode.</param>
/// <param name="bytes">The byte span to hold the encoded bytes.</param>
/// <param name="bytesWritten">Upon successful completion of the operation, the number of bytes encoded into <paramref name="bytes"/>.</param>
/// <returns><see langword="true"/> if all of the characters were encoded into the destination; <see langword="false"/> if the destination was too small to contain all the encoded bytes.</returns>
internal virtual bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
public virtual bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
{
int required = GetByteCount(chars);
if (required <= bytes.Length)
Expand Down Expand Up @@ -883,6 +882,24 @@ public virtual unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars)
}
}

/// <summary>Decodes into a span of chars a set of bytes from the specified read-only span if the destination is large enough.</summary>
/// <param name="bytes">A read-only span containing the sequence of bytes to decode.</param>
/// <param name="chars">The character span receiving the decoded bytes.</param>
/// <param name="charsWritten">Upon successful completion of the operation, the number of chars decoded into <paramref name="chars"/>.</param>
/// <returns><see langword="true"/> if all of the characters were decoded into the destination; <see langword="false"/> if the destination was too small to contain all the decoded chars.</returns>
public virtual bool TryGetChars(ReadOnlySpan<byte> bytes, Span<char> 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,8 @@ public override unsafe int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes)
}
}

// TODO https://github.com/dotnet/runtime/issues/84425: Make this public.
/// <summary>Encodes into a span of bytes a set of characters from the specified read-only span if the destination is large enough.</summary>
/// <param name="chars">The span containing the set of characters to encode.</param>
/// <param name="bytes">The byte span to hold the encoded bytes.</param>
/// <param name="bytesWritten">Upon successful completion of the operation, the number of bytes encoded into <paramref name="bytes"/>.</param>
/// <returns><see langword="true"/> if all of the characters were encoded into the destination; <see langword="false"/> if the destination was too small to contain all the encoded bytes.</returns>
internal override unsafe bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
/// <inheritdoc/>
public override unsafe bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
{
fixed (char* charsPtr = &MemoryMarshal.GetReference(chars))
fixed (byte* bytesPtr = &MemoryMarshal.GetReference(bytes))
Expand Down Expand Up @@ -534,6 +529,23 @@ public override unsafe int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars)
}
}

/// <inheritdoc/>
public override unsafe bool TryGetChars(ReadOnlySpan<byte> bytes, Span<char> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ private unsafe string GetStringForSmallInput(byte[] bytes)
return new string(new ReadOnlySpan<char>(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<char> chars, Span<byte> bytes, out int bytesWritten)
/// <inheritdoc/>
public override unsafe bool TryGetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, out int bytesWritten)
{
return base.TryGetBytes(chars, bytes, out bytesWritten);
}
Expand Down
Loading

0 comments on commit a4ae184

Please sign in to comment.