Skip to content

Add APIs to BlobBuilder for customizing the underlying byte array et al. #115294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
fd2a7e2
Remove dead code.
teo-tsirpanis Jan 5, 2025
d1b3914
Add extensibility points for custom blob builders.
teo-tsirpanis Jan 5, 2025
776a69f
Add APIs to `BlobBuilder` for customizing the underlying byte array.
teo-tsirpanis Jan 5, 2025
655ad38
Add tests.
teo-tsirpanis Jan 12, 2025
7e419b0
Support chunking in `WriteBytes(byte, int)` and when writing streams.
teo-tsirpanis Jan 12, 2025
8c078c7
Use framework methods where available.
teo-tsirpanis Jan 12, 2025
0f8c96a
Use framework methods to encode to UTF-8, and support chunking in `Wr…
teo-tsirpanis Jan 12, 2025
ff7ea5e
Zero-initialize buffers returned by `BlobBuilder.ReserveBytes`.
teo-tsirpanis Jan 13, 2025
31f3f24
Remove unnecessary pinning.
teo-tsirpanis Feb 6, 2025
a9443c6
Simplify chunking logic with an `IBufferWriter`-like pattern.
teo-tsirpanis May 4, 2025
07987e4
Fix wrong check order.
teo-tsirpanis May 5, 2025
125164d
Use a reasonable default max chunk size.
teo-tsirpanis May 5, 2025
bc81ac5
Double the size of each new chunk until we get to the max chunk size.
teo-tsirpanis May 5, 2025
ed979d9
Check that buffer size is not too small.
teo-tsirpanis May 5, 2025
515e825
Remove dependency to `Microsoft.Bcl.Memory`.
teo-tsirpanis May 9, 2025
5bd0a1d
Ensure `BlobBuilder.WriteUTF8`'s loop makes progress.
teo-tsirpanis May 10, 2025
4c5ea72
Fix tests.
teo-tsirpanis May 10, 2025
c0cb357
Clean-up a test.
teo-tsirpanis May 10, 2025
0633ea5
Remove `Microsoft.Bcl.Memory` from the solution.
teo-tsirpanis May 10, 2025
2ea1121
Remove unnecessary `unsafe`.
teo-tsirpanis May 10, 2025
82bd703
Fix misunderstanding of `Capacity` property.
teo-tsirpanis May 10, 2025
2e8f025
Update the downlevel `WriteUtf8` to use unsafe code.
teo-tsirpanis May 10, 2025
6fa2fdf
Call `OnLinking` earlier.
teo-tsirpanis May 10, 2025
4f7ebd8
Fix tests on .NET Framework.
teo-tsirpanis May 11, 2025
9123d14
Fix reference assembly typos.
teo-tsirpanis May 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{2231787B-18C9-493C-A102-1E0E6A3D2CD3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections.Immutable", "..\System.Collections.Immutable\ref\System.Collections.Immutable.csproj", "{282C76D4-54C5-44BF-9F15-1A4302234DBB}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,11 @@ public readonly partial struct Blob
}
public partial class BlobBuilder
{
protected BlobBuilder(byte[] buffer, int maxChunkSize = 0) { }
public BlobBuilder(int capacity = 256) { }
protected internal int ChunkCapacity { get { throw null; } }
protected byte[] Buffer { get { throw null; } set { } }
public int Capacity { get { throw null; } set { } }
public int Count { get { throw null; } }
protected int FreeBytes { get { throw null; } }
public void Align(int alignment) { }
Expand All @@ -238,8 +241,10 @@ protected virtual void FreeChunk() { }
public System.Reflection.Metadata.BlobBuilder.Blobs GetBlobs() { throw null; }
public void LinkPrefix(System.Reflection.Metadata.BlobBuilder prefix) { }
public void LinkSuffix(System.Reflection.Metadata.BlobBuilder suffix) { }
protected virtual void OnLinking(System.Reflection.Metadata.BlobBuilder other) { }
public void PadTo(int position) { }
public System.Reflection.Metadata.Blob ReserveBytes(int byteCount) { throw null; }
protected virtual void SetCapacity(int capacity) { throw null; }
public byte[] ToArray() { throw null; }
public byte[] ToArray(int start, int byteCount) { throw null; }
public System.Collections.Immutable.ImmutableArray<byte> ToImmutableArray() { throw null; }
Expand All @@ -253,6 +258,7 @@ public void WriteBytes(byte[] buffer) { }
public void WriteBytes(byte[] buffer, int start, int byteCount) { }
public void WriteBytes(System.Collections.Immutable.ImmutableArray<byte> buffer) { }
public void WriteBytes(System.Collections.Immutable.ImmutableArray<byte> buffer, int start, int byteCount) { }
public void WriteBytes(System.ReadOnlySpan<byte> buffer) { }
public void WriteCompressedInteger(int value) { }
public void WriteCompressedSignedInteger(int value) { }
public void WriteConstant(object? value) { }
Expand Down Expand Up @@ -2803,7 +2809,9 @@ public MetadataAggregator(System.Reflection.Metadata.MetadataReader baseReader,
}
public sealed partial class MetadataBuilder
{
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
public MetadataBuilder(int userStringHeapStartOffset = 0, int stringHeapStartOffset = 0, int blobHeapStartOffset = 0, int guidHeapStartOffset = 0) { }
public MetadataBuilder(int userStringHeapStartOffset = 0, int stringHeapStartOffset = 0, int blobHeapStartOffset = 0, int guidHeapStartOffset = 0, System.Func<int, System.Reflection.Metadata.BlobBuilder>? createBlobBuilderFunc = null) { }
public System.Reflection.Metadata.AssemblyDefinitionHandle AddAssembly(System.Reflection.Metadata.StringHandle name, System.Version version, System.Reflection.Metadata.StringHandle culture, System.Reflection.Metadata.BlobHandle publicKey, System.Reflection.AssemblyFlags flags, System.Reflection.AssemblyHashAlgorithm hashAlgorithm) { throw null; }
public System.Reflection.Metadata.AssemblyFileHandle AddAssemblyFile(System.Reflection.Metadata.StringHandle name, System.Reflection.Metadata.BlobHandle hashValue, bool containsMetadata) { throw null; }
public System.Reflection.Metadata.AssemblyReferenceHandle AddAssemblyReference(System.Reflection.Metadata.StringHandle name, System.Version version, System.Reflection.Metadata.StringHandle culture, System.Reflection.Metadata.BlobHandle publicKeyOrToken, System.Reflection.AssemblyFlags flags, System.Reflection.Metadata.BlobHandle hashValue) { throw null; }
Expand Down Expand Up @@ -3263,6 +3271,7 @@ internal CorHeader() { }
public sealed partial class DebugDirectoryBuilder
{
public DebugDirectoryBuilder() { }
public DebugDirectoryBuilder(System.Reflection.Metadata.BlobBuilder blobBuilder) { }
public void AddCodeViewEntry(string pdbPath, System.Reflection.Metadata.BlobContentId pdbContentId, ushort portablePdbVersion) { }
public void AddCodeViewEntry(string pdbPath, System.Reflection.Metadata.BlobContentId pdbContentId, ushort portablePdbVersion, int age) { }
public void AddEmbeddedPortablePdbEntry(System.Reflection.Metadata.BlobBuilder debugMetadata, ushort portablePdbVersion) { }
Expand Down Expand Up @@ -3356,6 +3365,7 @@ public partial class ManagedPEBuilder : System.Reflection.PortableExecutable.PEB
public const int ManagedResourcesDataAlignment = 8;
public const int MappedFieldDataAlignment = 8;
public ManagedPEBuilder(System.Reflection.PortableExecutable.PEHeaderBuilder header, System.Reflection.Metadata.Ecma335.MetadataRootBuilder metadataRootBuilder, System.Reflection.Metadata.BlobBuilder ilStream, System.Reflection.Metadata.BlobBuilder? mappedFieldData = null, System.Reflection.Metadata.BlobBuilder? managedResources = null, System.Reflection.PortableExecutable.ResourceSectionBuilder? nativeResources = null, System.Reflection.PortableExecutable.DebugDirectoryBuilder? debugDirectoryBuilder = null, int strongNameSignatureSize = 128, System.Reflection.Metadata.MethodDefinitionHandle entryPoint = default(System.Reflection.Metadata.MethodDefinitionHandle), System.Reflection.PortableExecutable.CorFlags flags = System.Reflection.PortableExecutable.CorFlags.ILOnly, System.Func<System.Collections.Generic.IEnumerable<System.Reflection.Metadata.Blob>, System.Reflection.Metadata.BlobContentId>? deterministicIdProvider = null) : base (default(System.Reflection.PortableExecutable.PEHeaderBuilder), default(System.Func<System.Collections.Generic.IEnumerable<System.Reflection.Metadata.Blob>, System.Reflection.Metadata.BlobContentId>)) { }
protected virtual System.Reflection.Metadata.BlobBuilder CreateBlobBuilder(int minimumSize = 0) { throw null; }
protected override System.Collections.Immutable.ImmutableArray<System.Reflection.PortableExecutable.PEBuilder.Section> CreateSections() { throw null; }
protected internal override System.Reflection.PortableExecutable.PEDirectoriesBuilder GetDirectories() { throw null; }
protected override System.Reflection.Metadata.BlobBuilder SerializeSection(string name, System.Reflection.PortableExecutable.SectionLocation location) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,7 @@
<data name="Arg_NotSimpleTypeName" xml:space="preserve">
<value>'{0}' is not a simple TypeName.</value>
</data>
<data name="BuilderBufferTooSmall" xml:space="preserve">
<value>Array passed to BlobBuilder must be at least 16 bytes long.</value>
</data>
</root>
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics;
using System.Reflection.Internal;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
#if NET
using System.Text.Unicode;
#endif

namespace System.Reflection
{
Expand Down Expand Up @@ -114,57 +119,96 @@ public static void WriteGuid(this byte[] buffer, int start, Guid value)
#endif
}

public static void WriteUTF8(this byte[] buffer, int start, char* charPtr, int charCount, int byteCount, bool allowUnpairedSurrogates)
#if NET
public static void WriteUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int charsRead, out int bytesWritten, bool allowUnpairedSurrogates)
{
Debug.Assert(byteCount >= charCount);
const char ReplacementCharacter = '\uFFFD';
int sourceLength = source.Length;
int destinationLength = destination.Length;

char* strEnd = charPtr + charCount;
fixed (byte* bufferPtr = &buffer[0])
while (true)
{
byte* ptr = bufferPtr + start;
OperationStatus status = Utf8.FromUtf16(source, destination, out int consumed, out int written, replaceInvalidSequences: !allowUnpairedSurrogates, isFinalBlock: true);
source = source.Slice(consumed);
destination = destination.Slice(written);

if (byteCount == charCount)
if (status <= OperationStatus.DestinationTooSmall)
{
while (charPtr < strEnd)
{
Debug.Assert(*charPtr <= 0x7f);
*ptr++ = unchecked((byte)*charPtr++);
}
break;
}
else

// NeedsMoreData is not expected because isFinalBlock is set to true.
Debug.Assert(status == OperationStatus.InvalidData);
// If we don't allow unpaired surrogates, they should have been replaced by FromUtf16.
Debug.Assert(allowUnpairedSurrogates);
char c = source[0];
Debug.Assert(char.IsSurrogate(c));
if (destination.Length < 3)
{
while (charPtr < strEnd)
{
char c = *charPtr++;
break;
}
destination[0] = (byte)(((c >> 12) & 0xF) | 0xE0);
destination[1] = (byte)(((c >> 6) & 0x3F) | 0x80);
destination[2] = (byte)((c & 0x3F) | 0x80);
source = source.Slice(1);
destination = destination.Slice(3);
}

charsRead = sourceLength - source.Length;
bytesWritten = destinationLength - destination.Length;
}
#else
public static unsafe void WriteUtf8(ReadOnlySpan<char> source, Span<byte> destination, out int charsRead, out int bytesWritten, bool allowUnpairedSurrogates)
{
const char ReplacementCharacter = '\uFFFD';

int sourceLength = source.Length;
int destinationLength = destination.Length;

fixed (char* pSource = &MemoryMarshal.GetReference(source))
fixed (byte* pDestination = &MemoryMarshal.GetReference(destination))
{
char* src = pSource, srcEnd = pSource + source.Length;
byte* dst = pDestination, dstEnd = pDestination + destination.Length;

if (c < 0x80)
while (src < srcEnd)
{
char c = *src;
if (c < 0x80)
{
if (dstEnd - dst < 1)
{
*ptr++ = (byte)c;
continue;
break;
}

if (c < 0x800)
*dst++ = (byte)c;
src++;
}
else if (c < 0x7FF)
{
if (dstEnd - dst < 2)
{
ptr[0] = (byte)(((c >> 6) & 0x1F) | 0xC0);
ptr[1] = (byte)((c & 0x3F) | 0x80);
ptr += 2;
continue;
break;
}

if (IsSurrogateChar(c))
*dst++ = (byte)((c >> 6) | 0xC0);
*dst++ = (byte)((c & 0x3F) | 0x80);
src++;
}
else
{
if (char.IsSurrogate(c))
{
// surrogate pair
if (IsHighSurrogateChar(c) && charPtr < strEnd && IsLowSurrogateChar(*charPtr))
if (char.IsHighSurrogate(c) && src - srcEnd < 2 && src[1] is char cLow && char.IsLowSurrogate(cLow))
{
int highSurrogate = c;
int lowSurrogate = *charPtr++;
int codepoint = (((highSurrogate - 0xd800) << 10) + lowSurrogate - 0xdc00) + 0x10000;
ptr[0] = (byte)(((codepoint >> 18) & 0x7) | 0xF0);
ptr[1] = (byte)(((codepoint >> 12) & 0x3F) | 0x80);
ptr[2] = (byte)(((codepoint >> 6) & 0x3F) | 0x80);
ptr[3] = (byte)((codepoint & 0x3F) | 0x80);
ptr += 4;
if (dstEnd - dst < 4)
{
break;
}
int codepoint = ((c - 0xd800) << 10) + cLow - 0xdc00 + 0x10000;
*dst++ = (byte)((codepoint >> 18) | 0xF0);
*dst++ = (byte)(((codepoint >> 12) & 0x3F) | 0x80);
*dst++ = (byte)(((codepoint >> 6) & 0x3F) | 0x80);
*dst++ = (byte)((codepoint & 0x3F) | 0x80);
src += 2;
continue;
}

Expand All @@ -175,87 +219,32 @@ public static void WriteUTF8(this byte[] buffer, int start, char* charPtr, int c
}
}

ptr[0] = (byte)(((c >> 12) & 0xF) | 0xE0);
ptr[1] = (byte)(((c >> 6) & 0x3F) | 0x80);
ptr[2] = (byte)((c & 0x3F) | 0x80);
ptr += 3;
if (dstEnd - dst < 3)
{
break;
}
*dst++ = (byte)((c >> 12) | 0xE0);
*dst++ = (byte)(((c >> 6) & 0x3F) | 0x80);
*dst++ = (byte)((c & 0x3F) | 0x80);
src++;
}
}

Debug.Assert(ptr == bufferPtr + start + byteCount);
Debug.Assert(charPtr == strEnd);
}
}

internal static unsafe int GetUTF8ByteCount(string str)
{
fixed (char* ptr = str)
{
return GetUTF8ByteCount(ptr, str.Length);
charsRead = (int)(src - pSource);
bytesWritten = (int)(dst - pDestination);
}
}
#endif

internal static unsafe int GetUTF8ByteCount(char* str, int charCount)
{
return GetUTF8ByteCount(str, charCount, int.MaxValue, out _);
}

internal static int GetUTF8ByteCount(char* str, int charCount, int byteLimit, out char* remainder)
#if !NET
internal static unsafe int GetByteCount(this Encoding encoding, ReadOnlySpan<char> str)
{
char* end = str + charCount;

char* ptr = str;
int byteCount = 0;
while (ptr < end)
fixed (char* ptr = &MemoryMarshal.GetReference(str))
{
int characterSize;
char c = *ptr++;
if (c < 0x80)
{
characterSize = 1;
}
else if (c < 0x800)
{
characterSize = 2;
}
else if (IsHighSurrogateChar(c) && ptr < end && IsLowSurrogateChar(*ptr))
{
// surrogate pair:
characterSize = 4;
ptr++;
}
else
{
characterSize = 3;
}

if (byteCount + characterSize > byteLimit)
{
ptr -= (characterSize < 4) ? 1 : 2;
break;
}

byteCount += characterSize;
return encoding.GetByteCount(ptr, str.Length);
}

remainder = ptr;
return byteCount;
}

internal static bool IsSurrogateChar(int c)
{
return unchecked((uint)(c - 0xD800)) <= 0xDFFF - 0xD800;
}

internal static bool IsHighSurrogateChar(int c)
{
return unchecked((uint)(c - 0xD800)) <= 0xDBFF - 0xD800;
}

internal static bool IsLowSurrogateChar(int c)
{
return unchecked((uint)(c - 0xDC00)) <= 0xDFFF - 0xDC00;
}
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ValidateRange(int bufferLength, int start, int byteCount, string byteCountParameterName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,29 +46,6 @@ internal static int TryReadAll(this Stream stream, byte[] buffer, int offset, in
return totalBytesRead;
}

#if NET
internal static int TryReadAll(this Stream stream, Span<byte> buffer)
#if NET
=> stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: false);
#else
{
int totalBytesRead = 0;
while (totalBytesRead < buffer.Length)
{
int bytesRead = stream.Read(buffer.Slice(totalBytesRead));
if (bytesRead == 0)
{
break;
}

totalBytesRead += bytesRead;
}

return totalBytesRead;
}
#endif
#endif

/// <summary>
/// Resolve image size as either the given user-specified size or distance from current position to end-of-stream.
/// Also performs the relevant argument validation and publicly visible caller has same argument names.
Expand Down
Loading
Loading