diff --git a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs index cef8ccfcab6299..2755c5be018bc5 100644 --- a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs +++ b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs @@ -227,8 +227,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 internal 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) { } @@ -240,6 +243,7 @@ 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; } public byte[] ToArray() { throw null; } @@ -255,6 +259,7 @@ public void WriteBytes(byte[] buffer) { } public void WriteBytes(byte[] buffer, int start, int byteCount) { } public void WriteBytes(System.Collections.Immutable.ImmutableArray buffer) { } public void WriteBytes(System.Collections.Immutable.ImmutableArray buffer, int start, int byteCount) { } + public void WriteBytes(System.ReadOnlySpan buffer) { } public void WriteCompressedInteger(int value) { } public void WriteCompressedSignedInteger(int value) { } public void WriteConstant(object? value) { } @@ -2805,7 +2810,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? 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; } @@ -3265,6 +3272,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) { } @@ -3358,6 +3366,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.Reflection.Metadata.BlobContentId>? deterministicIdProvider = null) : base (default(System.Reflection.PortableExecutable.PEHeaderBuilder), default(System.Func, System.Reflection.Metadata.BlobContentId>)) { } + protected virtual System.Reflection.Metadata.BlobBuilder CreateBlobBuilder(int minimumSize = 0) { throw null; } protected override System.Collections.Immutable.ImmutableArray 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; } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs index be4c38b380e5b0..81d8c786587126 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/BlobUtilities.cs @@ -5,6 +5,13 @@ using System.Diagnostics; using System.Reflection.Internal; using System.Runtime.CompilerServices; +#if NET +using System.Buffers; +using System.Text.Unicode; +#else +using System.Runtime.InteropServices; +using System.Text; +#endif namespace System.Reflection { @@ -61,125 +68,135 @@ public static void WriteGuid(this byte[] buffer, int start, Guid value) Debug.Assert(written); } - public static unsafe void WriteUTF8(this byte[] buffer, int start, char* charPtr, int charCount, int byteCount, bool allowUnpairedSurrogates) +#if NET + public static void WriteUtf8(ReadOnlySpan source, Span 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 void WriteUtf8(ReadOnlySpan source, Span destination, out int charsRead, out int bytesWritten, bool allowUnpairedSurrogates) + { + const char ReplacementCharacter = '\uFFFD'; + + int sourceLength = source.Length; + int destinationLength = destination.Length; + + unsafe + { + 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; + + while (src < srcEnd) + { + char c = *src; if (c < 0x80) { - *ptr++ = (byte)c; - continue; + if (dstEnd - dst < 1) + { + break; + } + *dst++ = (byte)c; + src++; } - - if (c < 0x800) + else if (c < 0x800) { - ptr[0] = (byte)(((c >> 6) & 0x1F) | 0xC0); - ptr[1] = (byte)((c & 0x3F) | 0x80); - ptr += 2; - continue; + if (dstEnd - dst < 2) + { + break; + } + *dst++ = (byte)((c >> 6) | 0xC0); + *dst++ = (byte)((c & 0x3F) | 0x80); + src++; } - - if (char.IsSurrogate(c)) + else { - // surrogate pair - if (char.IsHighSurrogate(c) && charPtr < strEnd && char.IsLowSurrogate(*charPtr)) + if (char.IsSurrogate(c)) { - 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; - continue; + // surrogate pair + if (char.IsHighSurrogate(c) && srcEnd - src >= 2 && src[1] is char cLow && char.IsLowSurrogate(cLow)) + { + 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; + } + + // unpaired high/low surrogate + if (!allowUnpairedSurrogates) + { + c = ReplacementCharacter; + } } - // unpaired high/low surrogate - if (!allowUnpairedSurrogates) + if (dstEnd - dst < 3) { - c = ReplacementCharacter; + break; } + *dst++ = (byte)((c >> 12) | 0xE0); + *dst++ = (byte)(((c >> 6) & 0x3F) | 0x80); + *dst++ = (byte)((c & 0x3F) | 0x80); + src++; } - - ptr[0] = (byte)(((c >> 12) & 0xF) | 0xE0); - ptr[1] = (byte)(((c >> 6) & 0x3F) | 0x80); - ptr[2] = (byte)((c & 0x3F) | 0x80); - ptr += 3; } - } - Debug.Assert(ptr == bufferPtr + start + byteCount); - Debug.Assert(charPtr == strEnd); + charsRead = (int)(src - pSource); + bytesWritten = (int)(dst - pDestination); + } } } +#endif - internal static unsafe int GetUTF8ByteCount(char* str, int charCount) +#if !NET + internal static unsafe int GetByteCount(this Encoding encoding, ReadOnlySpan str) { - return GetUTF8ByteCount(str, charCount, int.MaxValue, out _); - } - - internal static unsafe int GetUTF8ByteCount(char* str, int charCount, int byteLimit, out char* remainder) - { - 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 (char.IsHighSurrogate(c) && ptr < end && char.IsLowSurrogate(*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; } +#endif [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static void ValidateRange(int bufferLength, int start, int byteCount, string byteCountParameterName) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs index 21628b52aa48f1..2bdbd1cfc0253c 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobBuilder.cs @@ -7,6 +7,7 @@ using System.Reflection.Internal; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; namespace System.Reflection.Metadata { @@ -20,6 +21,8 @@ public partial class BlobBuilder internal const int DefaultChunkSize = 256; + internal const int DefaultMaxChunkSize = 8192; + // Must be at least the size of the largest primitive type we write atomically (Guid). internal const int MinChunkSize = 16; @@ -46,6 +49,9 @@ public partial class BlobBuilder // Non-head: highest bit is 1, lower 31 bits are not all 0. private uint _length; + // The maximum size of a chunk when writing a large amount of bytes. + private readonly int _maxChunkSize; + private const uint IsFrozenMask = 0x80000000; internal bool IsHead => (_length & IsFrozenMask) == 0; private int Length => (int)(_length & ~IsFrozenMask); @@ -61,6 +67,123 @@ public BlobBuilder(int capacity = DefaultChunkSize) _nextOrPrevious = this; _buffer = new byte[Math.Max(MinChunkSize, capacity)]; + _maxChunkSize = DefaultMaxChunkSize; + } + + /// + /// Creates a new that is underpinned by a preallocated byte array. + /// + /// The array that underpins the . + /// The size of chunks to split large writes into. + /// is less than 16. + protected BlobBuilder(byte[] buffer, int maxChunkSize = 0) + { + if (buffer is null) + { + Throw.ArgumentNull(nameof(buffer)); + } + if (maxChunkSize == 0) + { + maxChunkSize = DefaultMaxChunkSize; + } + if (maxChunkSize < MinChunkSize) + { + Throw.ArgumentOutOfRange(nameof(maxChunkSize)); + } + + _nextOrPrevious = this; + _buffer = buffer; + _maxChunkSize = maxChunkSize; + } + + /// + /// The byte array underpinning the . + /// + /// + /// + /// This property can only be accessed on the head of a chain of instances, + /// and should be accessed only within the context of overriding and + /// . + /// + /// + /// Setting this property will reset the chunk's length to zero. + /// + /// + protected internal byte[] Buffer + { + get + { + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + + return _buffer; + } + set + { + if (value is null) + { + Throw.ArgumentNull(nameof(value)); + } + + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + + // Do not check for the buffer's length. An implementation that pools blob builders separately from their buffers + // might want to set this to an empty array in FreeChunk, after returning the buffer to the pool (it will be set + // again to a new buffer in AllocateChunk). If AllocateChunk returns a builder with a too small buffer, we will + // detect it and throw in AllocateChunkHelper. + _buffer = value; + _length = 0; + } + } + + /// + /// The maximum number of bytes that can be contained in the memory allocated by the . + /// + /// The value is accessed while the + /// is not the head of a chain of + /// instances. + /// The value is set to less than + /// the value of . + public int Capacity + { + get + { + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + return _previousLengthOrFrozenSuffixLengthDelta + _buffer.Length; + } + set + { + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + if (value < Count) + { + Throw.ArgumentOutOfRange(nameof(value)); + } + if (value != _previousLengthOrFrozenSuffixLengthDelta + _buffer.Length) + { + // If the chunk is full, allocate a new chunk instead of resizing it in place. + // We could also expand it if it's "almost full", but setting the capacity usually happens + // at the beginning of using the builder, so it would not likely have a big impact. + if (FreeBytes == 0) + { + Expand(value - Count); + } + else + { + ResizeChunk(value - _previousLengthOrFrozenSuffixLengthDelta); + } + } + } } protected virtual BlobBuilder AllocateChunk(int minimalSize) @@ -73,6 +196,35 @@ protected virtual void FreeChunk() // nop } + private static void OnLinking(BlobBuilder left, BlobBuilder right) + { + left.OnLinking(right); + right.OnLinking(left); + } + + /// + /// Notifies when this instance is linked with another one. + /// + /// The other instance that gets linked. + /// + /// + /// Derived types can override this method to detect when a link is being made between two different types of + /// and take appropriate action. It is called before the underlying buffers are + /// linked, for both the current and the target instance. + /// + /// + /// Because the objects between instances may be swapped under + /// certain circumstances, if is overridden to return a + /// backed by a pooled buffer, it should check that uses the same pool, and throw an + /// exception otherwise. Applications that use such custom builders must ensure that the builders with different + /// buffer management strategies are not mixed together. + /// + /// + protected virtual void OnLinking(BlobBuilder other) + { + // nop + } + public void Clear() { if (!IsHead) @@ -399,6 +551,8 @@ public void LinkPrefix(BlobBuilder prefix) return; } + OnLinking(this, prefix); + PreviousLength += prefix.Count; // prefix is not a head anymore: @@ -460,6 +614,8 @@ public void LinkSuffix(BlobBuilder suffix) return; } + OnLinking(this, suffix); + bool isEmpty = Count == 0; // swap buffers of the heads: @@ -519,6 +675,49 @@ private void AddLength(int value) _length += (uint)value; } + /// + /// Returns a buffer to write new data into. You must call afterwards with the + /// number of bytes written. + /// + /// The minimum amount of bytes to return. + /// + /// Alongside , this method provides an API similar to . + /// + private ArraySegment GetWriteBuffer(int minBytes = 1) + { + if (FreeBytes < minBytes) + { + Expand(Math.Max(minBytes, Math.Min(Count, _maxChunkSize))); + } + return new ArraySegment(_buffer, Length, FreeBytes); + } + + private BlobBuilder AllocateChunkHelper(int newLength) + { + BlobBuilder newChunk = AllocateChunk(Math.Max(newLength, MinChunkSize)); + if (newChunk.ChunkCapacity < newLength) + { + // The overridden allocator didn't provide large enough buffer: + throw new InvalidOperationException(SR.Format(SR.ReturnedBuilderSizeTooSmall, GetType(), nameof(AllocateChunk))); + } + return newChunk; + } + + private void ResizeChunk(int newLength) + { + BlobBuilder newChunk = AllocateChunkHelper(newLength); + // While we are not strictly speaking "linking chunks", we call it to make sure that the blob builders + // are compatible with each other, as we are going to swap their buffers. + OnLinking(this, newChunk); + byte[] newBuffer = newChunk._buffer; + Array.Copy(_buffer, 0, newBuffer, 0, Length); + // Swap the buffers with the newly allocated BlobBuilder. + newChunk._buffer = _buffer; + _buffer = newBuffer; + // Free the new chunk that now contains the old buffer. + newChunk.ClearAndFreeChunk(); + } + [MethodImpl(MethodImplOptions.NoInlining)] private void Expand(int newLength) { @@ -531,13 +730,9 @@ private void Expand(int newLength) Throw.InvalidOperationBuilderAlreadyLinked(); } - var newChunk = AllocateChunk(Math.Max(newLength, MinChunkSize)); - if (newChunk.ChunkCapacity < newLength) - { - // The overridden allocator didn't provide large enough buffer: - throw new InvalidOperationException(SR.Format(SR.ReturnedBuilderSizeTooSmall, GetType(), nameof(AllocateChunk))); - } + BlobBuilder newChunk = AllocateChunkHelper(newLength); + OnLinking(this, newChunk); var newBuffer = newChunk._buffer; if (_length == 0) @@ -545,6 +740,8 @@ private void Expand(int newLength) // If the first write into an empty buffer needs more space than the buffer provides, swap the buffers. newChunk._buffer = _buffer; _buffer = newBuffer; + // Free the new chunk that now contains the old buffer. + newChunk.ClearAndFreeChunk(); } else { @@ -632,21 +829,21 @@ public void WriteBytes(byte value, int byteCount) Throw.InvalidOperationBuilderAlreadyLinked(); } - int bytesToCurrent = Math.Min(FreeBytes, byteCount); - - _buffer.WriteBytes(Length, value, bytesToCurrent); - AddLength(bytesToCurrent); - - int remaining = byteCount - bytesToCurrent; - if (remaining > 0) + while (byteCount > 0) { - Expand(remaining); - - _buffer.WriteBytes(0, value, remaining); - AddLength(remaining); + Span writeBuffer = GetWriteBuffer().AsSpan(); + int writeSize = Math.Min(byteCount, writeBuffer.Length); + writeBuffer.Slice(0, writeSize).Fill(value); + AddLength(writeSize); + byteCount -= writeSize; } } + /// + /// Writes bytes from a subset of the provided buffer into this . + /// + /// A pointer to the first byte of the buffer to write from. + /// The number of bytes to write from the buffer. /// is null. /// is negative. /// Builder is not writable, it has been linked with another one. @@ -672,19 +869,13 @@ public unsafe void WriteBytes(byte* buffer, int byteCount) private void WriteBytesUnchecked(ReadOnlySpan buffer) { - int bytesToCurrent = Math.Min(FreeBytes, buffer.Length); - - buffer.Slice(0, bytesToCurrent).CopyTo(_buffer.AsSpan(Length)); - - AddLength(bytesToCurrent); - - ReadOnlySpan remaining = buffer.Slice(bytesToCurrent); - if (!remaining.IsEmpty) + while (!buffer.IsEmpty) { - Expand(remaining.Length); - - remaining.CopyTo(_buffer); - AddLength(remaining.Length); + Span writeBuffer = GetWriteBuffer().AsSpan(); + int writeSize = Math.Min(buffer.Length, writeBuffer.Length); + buffer.Slice(0, writeSize).CopyTo(writeBuffer); + AddLength(writeSize); + buffer = buffer.Slice(writeSize); } } @@ -709,33 +900,28 @@ public int TryWriteBytes(Stream source, int byteCount) return 0; } - int bytesRead = 0; - int bytesToCurrent = Math.Min(FreeBytes, byteCount); + int remaining = byteCount; - if (bytesToCurrent > 0) + while (remaining > 0) { - bytesRead = source.TryReadAll(_buffer, Length, bytesToCurrent); + ArraySegment writeBuffer = GetWriteBuffer(); + int writeSize = Math.Min(remaining, writeBuffer.Count); + int bytesRead = source.TryReadAll(writeBuffer.Array!, writeBuffer.Offset, writeSize); AddLength(bytesRead); - - if (bytesRead != bytesToCurrent) + remaining -= bytesRead; + if (bytesRead != writeSize) { - return bytesRead; + break; } } - int remaining = byteCount - bytesToCurrent; - if (remaining > 0) - { - Expand(remaining); - bytesRead = source.TryReadAll(_buffer, 0, remaining); - AddLength(bytesRead); - - bytesRead += bytesToCurrent; - } - - return bytesRead; + return byteCount - remaining; } + /// + /// Writes bytes from the provided buffer into this . + /// + /// The buffer to write from. /// is null. /// Builder is not writable, it has been linked with another one. public void WriteBytes(ImmutableArray buffer) @@ -748,6 +934,12 @@ public void WriteBytes(ImmutableArray buffer) WriteBytes(buffer.AsSpan()); } + /// + /// Writes bytes from a subset of the provided buffer into this . + /// + /// The buffer to write from. + /// The index of the first byte to write from the buffer. + /// The number of bytes to write from the buffer. /// is null. /// Range specified by and falls outside of the bounds of the . /// Builder is not writable, it has been linked with another one. @@ -763,6 +955,10 @@ public void WriteBytes(ImmutableArray buffer, int start, int byteCount) WriteBytes(buffer.AsSpan(start, byteCount)); } + /// + /// Writes bytes from the provided buffer into this . + /// + /// The buffer to write from. /// is null. /// Builder is not writable, it has been linked with another one. public void WriteBytes(byte[] buffer) @@ -775,6 +971,12 @@ public void WriteBytes(byte[] buffer) WriteBytes(buffer.AsSpan()); } + /// + /// Writes bytes from a subset of the provided buffer into this . + /// + /// The buffer to write from. + /// The index of the first byte to write from the buffer. + /// The number of bytes to write from the buffer. /// is null. /// Range specified by and falls outside of the bounds of the . /// Builder is not writable, it has been linked with another one. @@ -790,7 +992,12 @@ public void WriteBytes(byte[] buffer, int start, int byteCount) WriteBytes(buffer.AsSpan(start, byteCount)); } - internal void WriteBytes(ReadOnlySpan buffer) + /// + /// Writes bytes from the provided buffer into this . + /// + /// The buffer to write from. + /// Builder is not writable, it has been linked with another one. + public void WriteBytes(ReadOnlySpan buffer) { if (!IsHead) { @@ -1028,7 +1235,7 @@ public void WriteSerializedString(string? value) return; } - WriteUTF8(value, 0, value.Length, allowUnpairedSurrogates: true, prependSize: true); + WriteUTF8(value.AsSpan(), allowUnpairedSurrogates: true, prependSize: true); } /// @@ -1070,48 +1277,28 @@ public void WriteUTF8(string value, bool allowUnpairedSurrogates = true) Throw.ArgumentNull(nameof(value)); } - WriteUTF8(value, 0, value.Length, allowUnpairedSurrogates, prependSize: false); + WriteUTF8(value.AsSpan(), allowUnpairedSurrogates, prependSize: false); } - internal unsafe void WriteUTF8(string str, int start, int length, bool allowUnpairedSurrogates, bool prependSize) + internal unsafe void WriteUTF8(ReadOnlySpan str, bool allowUnpairedSurrogates, bool prependSize) { - Debug.Assert(start >= 0); - Debug.Assert(length >= 0); - Debug.Assert(start + length <= str.Length); - if (!IsHead) { Throw.InvalidOperationBuilderAlreadyLinked(); } - fixed (char* strPtr = str) + if (prependSize) { - char* currentPtr = strPtr + start; - char* nextPtr; - - // the max size of compressed int is 4B: - int byteLimit = FreeBytes - (prependSize ? sizeof(uint) : 0); - - int bytesToCurrent = BlobUtilities.GetUTF8ByteCount(currentPtr, length, byteLimit, out nextPtr); - int charsToCurrent = (int)(nextPtr - currentPtr); - int charsToNext = length - charsToCurrent; - int bytesToNext = BlobUtilities.GetUTF8ByteCount(nextPtr, charsToNext); - - if (prependSize) - { - WriteCompressedInteger(bytesToCurrent + bytesToNext); - } - - _buffer.WriteUTF8(Length, currentPtr, charsToCurrent, bytesToCurrent, allowUnpairedSurrogates); - AddLength(bytesToCurrent); - - if (bytesToNext > 0) - { - Expand(bytesToNext); + WriteCompressedInteger(Encoding.UTF8.GetByteCount(str)); + } - _buffer.WriteUTF8(0, nextPtr, charsToNext, bytesToNext, allowUnpairedSurrogates); - AddLength(bytesToNext); - } + while (!str.IsEmpty) + { + // Request at least four bytes to guarantee writing at least one character per iteration. + Span writeBuffer = GetWriteBuffer(4).AsSpan(); + BlobUtilities.WriteUtf8(str, writeBuffer, out int charsConsumed, out int bytesWritten, allowUnpairedSurrogates); + AddLength(bytesWritten); + str = str.Slice(charsConsumed); } } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs index b143adf20a9604..59d034b043cbf3 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/BlobWriter.cs @@ -425,7 +425,7 @@ public void WriteSerializedString(string? str) return; } - WriteUTF8(str, 0, str.Length, allowUnpairedSurrogates: true, prependSize: true); + WriteUTF8(str, allowUnpairedSurrogates: true, prependSize: true); } /// @@ -462,24 +462,22 @@ public void WriteUTF8(string value, bool allowUnpairedSurrogates) Throw.ArgumentNull(nameof(value)); } - WriteUTF8(value, 0, value.Length, allowUnpairedSurrogates, prependSize: false); + WriteUTF8(value, allowUnpairedSurrogates, prependSize: false); } - private unsafe void WriteUTF8(string str, int start, int length, bool allowUnpairedSurrogates, bool prependSize) + private void WriteUTF8(string str, bool allowUnpairedSurrogates, bool prependSize) { - fixed (char* strPtr = str) + if (prependSize) { - char* charPtr = strPtr + start; - int byteCount = Encoding.UTF8.GetByteCount(charPtr, length); - - if (prependSize) - { - WriteCompressedInteger(byteCount); - } + WriteCompressedInteger(Encoding.UTF8.GetByteCount(str)); + } - int startOffset = Advance(byteCount); - _buffer.WriteUTF8(startOffset, charPtr, length, byteCount, allowUnpairedSurrogates); + BlobUtilities.WriteUtf8(str.AsSpan(), _buffer.AsSpan(_position), out int charsRead, out int bytesWritten, allowUnpairedSurrogates); + if (charsRead != str.Length) + { + Throw.OutOfBounds(); } + _position += bytesWritten; } /// diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs index 5bded8d832a13b..6f2bbba5948949 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.Heaps.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Reflection.Internal; using System.Runtime.InteropServices; using System.Text; @@ -11,30 +12,10 @@ namespace System.Reflection.Metadata.Ecma335 { public sealed partial class MetadataBuilder { - private sealed class HeapBlobBuilder : BlobBuilder - { - private int _capacityExpansion; - - public HeapBlobBuilder(int capacity) - : base(capacity) - { - } - - protected override BlobBuilder AllocateChunk(int minimalSize) - { - return new HeapBlobBuilder(Math.Max(Math.Max(minimalSize, ChunkCapacity), _capacityExpansion)); - } - - internal void SetCapacity(int capacity) - { - _capacityExpansion = Math.Max(0, capacity - Count - FreeBytes); - } - } - // #US heap private const int UserStringHeapSizeLimit = 0x01000000; private readonly Dictionary _userStrings = new Dictionary(256); - private readonly HeapBlobBuilder _userStringBuilder = new HeapBlobBuilder(4 * 1024); + private readonly BlobBuilder _userStringBuilder; private readonly int _userStringHeapStartOffset; // #String heap @@ -49,7 +30,9 @@ internal void SetCapacity(int capacity) // #GUID heap private readonly Dictionary _guids = new Dictionary(); - private readonly HeapBlobBuilder _guidBuilder = new HeapBlobBuilder(16); // full metadata has just a single guid + private readonly BlobBuilder _guidBuilder; + + private readonly Func _createBlobBuilderFunc; /// /// Creates a builder for metadata tables and heaps. @@ -73,11 +56,45 @@ internal void SetCapacity(int capacity) /// Offset is too big. /// Offset is negative. /// is not a multiple of size of GUID. + [EditorBrowsable(EditorBrowsableState.Never)] + public MetadataBuilder( + int userStringHeapStartOffset, + int stringHeapStartOffset, + int blobHeapStartOffset, + int guidHeapStartOffset) + : this(userStringHeapStartOffset, stringHeapStartOffset, blobHeapStartOffset, guidHeapStartOffset, null) { } + + /// + /// Creates a builder for metadata tables and heaps. + /// + /// + /// Start offset of the User String heap. + /// The cumulative size of User String heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata. + /// + /// + /// Start offset of the String heap. + /// The cumulative size of String heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata. + /// + /// + /// Start offset of the Blob heap. + /// The cumulative size of Blob heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata. + /// + /// + /// Start offset of the Guid heap. + /// The cumulative size of Guid heaps of all previous EnC generations. Should be 0 unless the metadata is EnC delta metadata. + /// + /// + /// Optional factory to customize creating instances with the specified capacity. + /// + /// Offset is too big. + /// Offset is negative. + /// is not a multiple of size of GUID. public MetadataBuilder( int userStringHeapStartOffset = 0, int stringHeapStartOffset = 0, int blobHeapStartOffset = 0, - int guidHeapStartOffset = 0) + int guidHeapStartOffset = 0, + Func? createBlobBuilderFunc = null) { // -1 for the 0 we always write at the beginning of the heap: if (userStringHeapStartOffset >= UserStringHeapSizeLimit - 1) @@ -110,6 +127,10 @@ public MetadataBuilder( throw new ArgumentException(SR.Format(SR.ValueMustBeMultiple, BlobUtilities.SizeOfGuid), nameof(guidHeapStartOffset)); } + _createBlobBuilderFunc = createBlobBuilderFunc ?? (capacity => new BlobBuilder(capacity)); + _userStringBuilder = _createBlobBuilderFunc(4 * 1024); + _guidBuilder = _createBlobBuilderFunc(BlobUtilities.SizeOfGuid); // full metadata has just a single guid + // Add zero-th entry to all heaps, even in EnC delta. // We don't want generation-relative handles to ever be IsNil. // In both full and delta metadata all nil heap handles should have zero value. @@ -156,7 +177,10 @@ public void SetCapacity(HeapIndex heap, int byteCount) break; case HeapIndex.Guid: - _guidBuilder.SetCapacity(byteCount); + if (byteCount > _guidBuilder.Count) + { + _guidBuilder.Capacity = byteCount; + } break; case HeapIndex.String: @@ -164,7 +188,10 @@ public void SetCapacity(HeapIndex heap, int byteCount) break; case HeapIndex.UserString: - _userStringBuilder.SetCapacity(byteCount); + if (byteCount > _userStringBuilder.Count) + { + _userStringBuilder.Capacity = byteCount; + } break; default: @@ -333,7 +360,7 @@ public BlobHandle GetOrAddDocumentName(string value) { int next = value.IndexOf(separator, i); - partBuilder.WriteUTF8(value, i, (next >= 0 ? next : value.Length) - i, allowUnpairedSurrogates: true, prependSize: false); + partBuilder.WriteUTF8(value.AsSpan(i, (next >= 0 ? next : value.Length) - i), allowUnpairedSurrogates: true, prependSize: false); resultBuilder.WriteCompressedInteger(GetOrAddBlob(partBuilder).GetHeapOffset()); if (next == -1) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.cs index c7bfdd82c5638c..357fbbea0d2d38 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataBuilder.cs @@ -11,7 +11,7 @@ public sealed partial class MetadataBuilder { internal SerializedMetadata GetSerializedMetadata(ImmutableArray externalRowCounts, int metadataVersionByteCount, bool isStandaloneDebugMetadata) { - var stringHeapBuilder = new HeapBlobBuilder(_stringHeapCapacity); + var stringHeapBuilder = _createBlobBuilderFunc(_stringHeapCapacity); var stringMap = SerializeStringHeap(stringHeapBuilder, _strings, _stringHeapStartOffset); Debug.Assert(HeapIndex.UserString == 0); diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/PooledBlobBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/PooledBlobBuilder.cs index 34510f4de44cb2..5b985ccb8637d1 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/PooledBlobBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/PooledBlobBuilder.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Reflection.Internal; namespace System.Reflection.Metadata @@ -10,10 +11,10 @@ internal sealed class PooledBlobBuilder : BlobBuilder private const int PoolSize = 128; private const int ChunkSize = 1024; - private static readonly ObjectPool s_chunkPool = new ObjectPool(() => new PooledBlobBuilder(ChunkSize), PoolSize); + private static readonly ObjectPool s_chunkPool = new ObjectPool(() => new PooledBlobBuilder(), PoolSize); - private PooledBlobBuilder(int size) - : base(size) + private PooledBlobBuilder() + : base([], ChunkSize) { } @@ -24,19 +25,26 @@ public static PooledBlobBuilder GetInstance() protected override BlobBuilder AllocateChunk(int minimalSize) { - if (minimalSize <= ChunkSize) - { - return s_chunkPool.Allocate(); - } - - return new BlobBuilder(minimalSize); + PooledBlobBuilder builder = s_chunkPool.Allocate(); + builder.Buffer = ArrayPool.Shared.Rent(minimalSize); + return builder; } protected override void FreeChunk() { + ArrayPool.Shared.Return(Buffer); + Buffer = []; s_chunkPool.Free(this); } + protected override void OnLinking(BlobBuilder other) + { + if (other is not PooledBlobBuilder) + { + throw new InvalidOperationException("Cannot link with a non-pooled builder."); + } + } + public new void Free() { base.Free(); diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs index 32cd1e6d8386e0..377a9b83ae01c9 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/DebugDirectory/DebugDirectoryBuilder.cs @@ -20,10 +20,24 @@ private struct Entry private readonly List _entries; private readonly BlobBuilder _dataBuilder; - public DebugDirectoryBuilder() + /// + /// Creates a instance. + /// + public DebugDirectoryBuilder() : this(new BlobBuilder()) { } + + /// + /// Creates a instance. + /// + /// User-provided instance to use. + public DebugDirectoryBuilder(BlobBuilder blobBuilder) { + if (blobBuilder is null) + { + Throw.ArgumentNull(nameof(blobBuilder)); + } + _entries = new List(3); - _dataBuilder = new BlobBuilder(); + _dataBuilder = blobBuilder; } internal void AddEntry(DebugDirectoryEntryType type, uint version, uint stamp, int dataSize) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedPEBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedPEBuilder.cs index 76195a7ea11b73..5cd8e4da29bdf1 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedPEBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/ManagedPEBuilder.cs @@ -83,7 +83,8 @@ public ManagedPEBuilder( { if (IsDeterministic) { - var builder = new DebugDirectoryBuilder(); + // The exact size of a reproducible debug entry: 7 4-byte integers, and no additional data (28 bytes). + var builder = new DebugDirectoryBuilder(CreateBlobBuilder(28)); builder.AddReproducibleEntry(); return builder; } @@ -91,6 +92,14 @@ public ManagedPEBuilder( return null; } + /// + /// Creates a for use by this instance. + /// Can be overridden in derived types to customize the logic. + /// + /// The builder's minimum initial capacity. + protected virtual BlobBuilder CreateBlobBuilder(int minimumSize = 0) => + minimumSize == 0 ? new BlobBuilder() : new BlobBuilder(minimumSize); + protected override ImmutableArray
CreateSections() { var builder = ImmutableArray.CreateBuilder
(3); @@ -120,8 +129,8 @@ protected override BlobBuilder SerializeSection(string name, SectionLocation loc private BlobBuilder SerializeTextSection(SectionLocation location) { - var sectionBuilder = new BlobBuilder(); - var metadataBuilder = new BlobBuilder(); + var sectionBuilder = CreateBlobBuilder(); + var metadataBuilder = CreateBlobBuilder(); var metadataSizes = _metadataRootBuilder.Sizes; @@ -144,7 +153,7 @@ private BlobBuilder SerializeTextSection(SectionLocation location) if (_debugDirectoryBuilderOpt != null) { int debugDirectoryOffset = textSection.ComputeOffsetToDebugDirectory(); - debugTableBuilderOpt = new BlobBuilder(_debugDirectoryBuilderOpt.TableSize); + debugTableBuilderOpt = CreateBlobBuilder(_debugDirectoryBuilderOpt.TableSize); _debugDirectoryBuilderOpt.Serialize(debugTableBuilderOpt, location, debugDirectoryOffset); // Only the size of the fixed part of the debug table goes here. @@ -186,7 +195,7 @@ private BlobBuilder SerializeResourceSection(SectionLocation location) { Debug.Assert(_nativeResourcesOpt != null); - var sectionBuilder = new BlobBuilder(); + var sectionBuilder = CreateBlobBuilder(); _nativeResourcesOpt.Serialize(sectionBuilder, location); _peDirectoriesBuilder.ResourceTable = new DirectoryEntry(location.RelativeVirtualAddress, sectionBuilder.Count); @@ -195,7 +204,7 @@ private BlobBuilder SerializeResourceSection(SectionLocation location) private BlobBuilder SerializeRelocationSection(SectionLocation location) { - var sectionBuilder = new BlobBuilder(); + var sectionBuilder = CreateBlobBuilder(); WriteRelocationSection(sectionBuilder, Header.Machine, _lazyEntryPointAddress); _peDirectoriesBuilder.BaseRelocationTable = new DirectoryEntry(location.RelativeVirtualAddress, sectionBuilder.Count); diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs index e0dc3d055c5f0e..c363f96c7fcb03 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Reflection.Internal; using System.Text; using Xunit; @@ -18,18 +17,27 @@ public void Ctor() { var builder = new BlobBuilder(); Assert.Equal(BlobBuilder.DefaultChunkSize, builder.ChunkCapacity); + Assert.Equal(BlobBuilder.DefaultChunkSize, builder.Capacity); builder = new BlobBuilder(0); Assert.Equal(BlobBuilder.MinChunkSize, builder.ChunkCapacity); + Assert.Equal(BlobBuilder.MinChunkSize, builder.Capacity); builder = new BlobBuilder(10001); Assert.Equal(10001, builder.ChunkCapacity); + Assert.Equal(10001, builder.Capacity); + + var buffer = new byte[1024]; + builder = new BlobBuilderWithEvents(buffer); + Assert.Same(buffer, builder.Buffer); } [Fact] public void Ctor_Errors() { Assert.Throws(() => new BlobBuilder(-1)); + Assert.Throws(() => new BlobBuilderWithEvents(new byte[1024], BlobBuilder.MinChunkSize - 1)); + Assert.Throws(() => new BlobBuilderWithEvents(new byte[1024], -1)); } [Fact] @@ -53,69 +61,69 @@ public void CountClear() AssertEx.Equal(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, builder.ToArray()); } - private void TestContentEquals(byte[] left, byte[] right) - { - var builder1 = new BlobBuilder(0); - builder1.WriteBytes(left); - - var builder2 = new BlobBuilder(0); - builder2.WriteBytes(right); - - bool expected = left.AsSpan().SequenceEqual(right.AsSpan()); - Assert.Equal(expected, builder1.ContentEquals(builder2)); - } - [Fact] public void ContentEquals() { var builder = new BlobBuilder(); Assert.True(builder.ContentEquals(builder)); - Assert.False(builder.ContentEquals(null)); + Assert.False(builder.ContentEquals((BlobBuilder)null)); - TestContentEquals(new byte[] { }, new byte[] { }); - TestContentEquals(new byte[] { 1 }, new byte[] { }); - TestContentEquals(new byte[] { }, new byte[] { 1 }); - TestContentEquals(new byte[] { 1 }, new byte[] { 1 }); + TestContentEquals([], []); + TestContentEquals([1], []); + TestContentEquals([], [1]); + TestContentEquals([1], [1]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); TestContentEquals( - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }, - new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }); + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 99, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]); + + static void TestContentEquals(ReadOnlySpan left, ReadOnlySpan right) + { + var builder1 = new BlobBuilder(0); + builder1.WriteBytes(left); + + var builder2 = new BlobBuilder(0); + builder2.WriteBytes(right); + + bool expected = left.SequenceEqual(right); + Assert.Equal(expected, builder1.ContentEquals(builder2)); + } } [Fact] @@ -125,20 +133,23 @@ public void GetBlobs() builder.WriteBytes(1, 100); var blobs = builder.GetBlobs().ToArray(); - Assert.Equal(2, blobs.Length); + Assert.Equal(4, blobs.Length); Assert.Equal(16, blobs[0].Length); - Assert.Equal(100 - 16, blobs[1].Length); + Assert.Equal(16, blobs[0].GetBytes().Array.Length); + Assert.Equal(16, blobs[1].Length); + Assert.Equal(16, blobs[1].GetBytes().Array.Length); + Assert.Equal(32, blobs[2].Length); + Assert.Equal(32, blobs[2].GetBytes().Array.Length); + Assert.Equal(36, blobs[3].Length); + Assert.Equal(64, blobs[3].GetBytes().Array.Length); - builder.WriteByte(1); + builder.WriteBytes(1, 64 - 36 + 1); blobs = builder.GetBlobs().ToArray(); - Assert.Equal(3, blobs.Length); - Assert.Equal(16, blobs[0].Length); - Assert.Equal(16, blobs[0].GetBytes().Array.Length); - Assert.Equal(100 - 16, blobs[1].Length); - Assert.Equal(100 - 16, blobs[1].GetBytes().Array.Length); - Assert.Equal(1, blobs[2].Length); - Assert.Equal(100 - 16, blobs[2].GetBytes().Array.Length); + Assert.Equal(5, blobs.Length); + Assert.Equal(64, blobs[3].Length); + Assert.Equal(1, blobs[4].Length); + Assert.Equal(128, blobs[4].GetBytes().Array.Length); builder.Clear(); @@ -157,18 +168,13 @@ public void GetChunks_DestructingEnum() { var builder = new BlobBuilder(16); - for (int i = 0; i < j; i++) - { - builder.WriteBytes((byte)i, 16); - } - - int n = 0; - foreach (var chunk in builder.GetChunks()) + builder.WriteBytes(0, 16); + for (int i = 0; i < j - 1; i++) { - n++; + builder.WriteBytes((byte)i, 16 << i); } - Assert.Equal(j, n); + Assert.Equal(j, builder.GetChunks().Count()); var chunks = new HashSet(); foreach (var chunk in builder.GetChunks()) @@ -983,43 +989,6 @@ public void WriteUTF8_ReplaceUnpairedSurrogates() }, writer.ToArray()); } - [Fact] - public void WriteUTF8_Substring() - { - var writer = new BlobBuilder(4); - writer.WriteUTF8("abc", 0, 0, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new byte[0], writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 0, 1, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'a' }, writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 0, 2, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'a', (byte)'b' }, writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 0, 3, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'a', (byte)'b', (byte)'c' }, writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 1, 0, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new byte[0], writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 1, 1, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'b' }, writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 1, 2, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'b', (byte)'c' }, writer.ToArray()); - writer.Clear(); - - writer.WriteUTF8("abc", 2, 1, allowUnpairedSurrogates: true, prependSize: false); - AssertEx.Equal(new[] { (byte)'c' }, writer.ToArray()); - writer.Clear(); - } - [Fact] public void EmptyWrites() { @@ -1043,11 +1012,14 @@ public void Pooled() builder2.WriteByte(2); builder3.WriteByte(3); + builder1.LinkPrefix(builder2); + + AssertEx.Equal((byte[])[2, 1], builder1.ToArray()); + // mix pooled with non-pooled - builder1.LinkPrefix(builder3); + Assert.Throws(() => builder1.LinkSuffix(builder3)); builder1.Free(); - builder2.Free(); } private class ProperStreamRead_TestStream : TestStreamBase @@ -1127,5 +1099,104 @@ public void LinkEmptySuffixAndPrefixShouldFreeThem() b1.LinkPrefix(b5); Assert.True(b4.IsHead); } + + [Fact] + public void OnLinkingGetsCalled() + { + var b1 = new BlobBuilderWithEvents(); + var b2 = new BlobBuilderWithEvents(); + var b3 = new BlobBuilderWithEvents(); + var b4 = new BlobBuilderWithEvents(); + + b1.WriteBytes(1, 1); + b2.WriteBytes(1, 1); + b3.WriteBytes(1, 1); + b4.WriteBytes(1, 1); + + b1.Linking += b => Assert.Same(b2, b); + b2.Linking += b => Assert.Same(b1, b); + b1.LinkSuffix(b2); + + b3.Linking += b => Assert.Same(b4, b); + b4.Linking += b => Assert.Same(b3, b); + b3.LinkPrefix(b4); + } + + [Fact] + public void SetCapacity() + { + var builder = new FixedChunkBlobBuilder(16); + Assert.Equal(16, builder.Capacity); + builder.WriteBytes(1, 15); + Assert.Equal(Enumerable.Repeat((byte)1, 15), builder.ToArray()); + builder.Capacity = 32; + Assert.Equal(32, builder.Capacity); + Assert.Equal(Enumerable.Repeat((byte)1, 15), builder.ToArray()); + + builder = new FixedChunkBlobBuilder(16); + builder.WriteBytes(1, 32); + Assert.Equal(Enumerable.Repeat((byte)1, 32), builder.ToArray()); + Assert.Throws(() => builder.Capacity = 16); + builder.Capacity = 64; + Assert.Equal(Enumerable.Repeat((byte)1, 32), builder.ToArray()); + builder.Capacity = 32; + Assert.Equal(Enumerable.Repeat((byte)1, 32), builder.ToArray()); + } + + [Fact] + public void Chunking() + { + const int ChunkSize = 128; + const byte TestValue = (byte)'a'; + const int TestSize = 1024; + + var b = new FixedChunkBlobBuilder(ChunkSize); + b.Buffer = []; // Test that setting an empty buffer works. + b.WriteBytes(Enumerable.Repeat(TestValue, TestSize).ToArray().AsSpan()); + AssertIsChunked(); + + b = new FixedChunkBlobBuilder(ChunkSize); + b.WriteBytes(TestValue, TestSize); + AssertIsChunked(); + + b = new FixedChunkBlobBuilder(ChunkSize); + int written = b.TryWriteBytes(new MemoryStream(Enumerable.Repeat(TestValue, TestSize).ToArray()), TestSize); + Assert.Equal(TestSize, written); + AssertIsChunked(); + + b = new FixedChunkBlobBuilder(ChunkSize); + b.WriteUTF8(new string((char)TestValue, TestSize)); + AssertIsChunked(); + + void AssertIsChunked() + { + Assert.Equal(TestSize, b.Count); + foreach (var chunk in b.GetBlobs()) + { + Assert.InRange(chunk.Length, 1, ChunkSize); + foreach (var x in chunk.GetBytes().AsSpan()) + { + Assert.Equal(TestValue, x); + } + } + } + } + + private sealed class FixedChunkBlobBuilder(int size) : BlobBuilder(new byte[size], size); + + private sealed class BlobBuilderWithEvents : BlobBuilder + { + public event Action? Linking; + + public BlobBuilderWithEvents() { } + + public BlobBuilderWithEvents(byte[] bytes, int maxChunkSize = 0) : base(bytes, maxChunkSize) { } + + protected override void OnLinking(BlobBuilder builder) + { + Linking?.Invoke(builder); + base.OnLinking(builder); + } + } } } diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobUtilitiesTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobUtilitiesTests.cs deleted file mode 100644 index 4fca10dfff0c27..00000000000000 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobUtilitiesTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Xunit; - -namespace System.Reflection.Metadata.Tests -{ - public unsafe class BlobUtilitiesTests - { - private void TestGetUTF8ByteCount(int expectedCount, string expectedRemainder, string str, int charCount, int byteLimit) - { - fixed (char* ptr = str) - { - char* remainderPtr; - Assert.Equal(expectedCount, BlobUtilities.GetUTF8ByteCount(ptr, charCount, byteLimit, out remainderPtr)); - - string remainder = new string(remainderPtr); - Assert.Equal(expectedRemainder, remainder); - } - } - - [Fact] - public void GetUTF8ByteCount() - { - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(2, "c", str: "abc", charCount: 2, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(2, "", str: "\u0123", charCount: 1, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(3, "", str: "\u1234", charCount: 1, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(3, "", str: "\ud800", charCount: 1, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(3, "", str: "\udc00", charCount: 1, byteLimit: int.MaxValue); - TestGetUTF8ByteCount(3, "\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: int.MaxValue); // surrogate pair - TestGetUTF8ByteCount(4, "", str: "\ud800\udc00", charCount: 2, byteLimit: int.MaxValue); // surrogate pair - - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: 0); - TestGetUTF8ByteCount(0, "abc", str: "abc", charCount: 2, byteLimit: 0); - TestGetUTF8ByteCount(0, "\u0123", str: "\u0123", charCount: 1, byteLimit: 0); - TestGetUTF8ByteCount(0, "\u1234", str: "\u1234", charCount: 1, byteLimit: 0); - TestGetUTF8ByteCount(0, "\ud800", str: "\ud800", charCount: 1, byteLimit: 0); - TestGetUTF8ByteCount(0, "\udc00", str: "\udc00", charCount: 1, byteLimit: 0); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: 0); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 2, byteLimit: 0); // surrogate pair - - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: 1); - TestGetUTF8ByteCount(1, "bc", str: "abc", charCount: 2, byteLimit: 1); - TestGetUTF8ByteCount(0, "\u0123", str: "\u0123", charCount: 1, byteLimit: 1); - TestGetUTF8ByteCount(0, "\u1234", str: "\u1234", charCount: 1, byteLimit: 1); - TestGetUTF8ByteCount(0, "\ud800", str: "\ud800", charCount: 1, byteLimit: 1); - TestGetUTF8ByteCount(0, "\udc00", str: "\udc00", charCount: 1, byteLimit: 1); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: 1); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 2, byteLimit: 1); // surrogate pair - - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: 2); - TestGetUTF8ByteCount(2, "c", str: "abc", charCount: 2, byteLimit: 2); - TestGetUTF8ByteCount(2, "", str: "\u0123", charCount: 1, byteLimit: 2); - TestGetUTF8ByteCount(0, "\u1234", str: "\u1234", charCount: 1, byteLimit: 2); - TestGetUTF8ByteCount(0, "\ud800", str: "\ud800", charCount: 1, byteLimit: 2); - TestGetUTF8ByteCount(0, "\udc00", str: "\udc00", charCount: 1, byteLimit: 2); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: 2); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 2, byteLimit: 2); // surrogate pair - - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: 3); - TestGetUTF8ByteCount(2, "c", str: "abc", charCount: 2, byteLimit: 3); - TestGetUTF8ByteCount(2, "", str: "\u0123", charCount: 1, byteLimit: 3); - TestGetUTF8ByteCount(3, "", str: "\u1234", charCount: 1, byteLimit: 3); - TestGetUTF8ByteCount(3, "", str: "\ud800", charCount: 1, byteLimit: 3); - TestGetUTF8ByteCount(3, "", str: "\udc00", charCount: 1, byteLimit: 3); - TestGetUTF8ByteCount(3, "\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: 3); - TestGetUTF8ByteCount(0, "\ud800\udc00", str: "\ud800\udc00", charCount: 2, byteLimit: 3); // surrogate pair - - TestGetUTF8ByteCount(0, "", str: "", charCount: 0, byteLimit: 4); - TestGetUTF8ByteCount(2, "c", str: "abc", charCount: 2, byteLimit: 4); - TestGetUTF8ByteCount(2, "", str: "\u0123", charCount: 1, byteLimit: 4); - TestGetUTF8ByteCount(3, "", str: "\u1234", charCount: 1, byteLimit: 4); - TestGetUTF8ByteCount(3, "", str: "\ud800", charCount: 1, byteLimit: 4); - TestGetUTF8ByteCount(3, "", str: "\udc00", charCount: 1, byteLimit: 4); - TestGetUTF8ByteCount(3, "\udc00", str: "\ud800\udc00", charCount: 1, byteLimit: 4); - TestGetUTF8ByteCount(4, "", str: "\ud800\udc00", charCount: 2, byteLimit: 4); // surrogate pair - } - } -} diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs index 58089ec0d573cd..47f1575dc2220c 100644 --- a/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/Metadata/Ecma335/MetadataBuilderTests.cs @@ -874,5 +874,17 @@ public void ValidateStateMachineMethodTable() builder.AddStateMachineMethod(MetadataTokens.MethodDefinitionHandle(1), default(MethodDefinitionHandle)); Assert.Throws(() => builder.ValidateOrder()); } + + [Fact] + public void CreateBlobBuilderFuncGetsCalled() + { + bool called = false; + _ = new MetadataBuilder(createBlobBuilderFunc: capacity => + { + called = true; + return new BlobBuilder(capacity); + }); + Assert.True(called); + } } } diff --git a/src/libraries/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs b/src/libraries/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs index 91ea39acb88cad..2d906861c7df01 100644 --- a/src/libraries/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs +++ b/src/libraries/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs @@ -661,7 +661,7 @@ public unsafe void NativeResources_BadImpl() [Fact] public void GetContentToSign_AllInOneBlob() { - var builder = new BlobBuilder(16); + var builder = new FixedChunkBlobBuilder(16); builder.WriteBytes(1, 5); var snFixup = builder.ReserveBytes(5); builder.WriteBytes(2, 6); @@ -680,7 +680,7 @@ public void GetContentToSign_AllInOneBlob() [Fact] public void GetContentToSign_MultiBlobHeader() { - var builder = new BlobBuilder(16); + var builder = new FixedChunkBlobBuilder(16); builder.WriteBytes(0, 16); builder.WriteBytes(1, 16); builder.WriteBytes(2, 16); @@ -707,7 +707,7 @@ public void GetContentToSign_MultiBlobHeader() [Fact] public void GetContentToSign_HeaderAndFixupInDistinctBlobs() { - var builder = new BlobBuilder(16); + var builder = new FixedChunkBlobBuilder(16); builder.WriteBytes(0, 16); builder.WriteBytes(1, 16); builder.WriteBytes(2, 16); @@ -916,5 +916,7 @@ public void GetSuffixBlob() Assert.Equal(2, b.Start); Assert.Equal(0, b.Length); } + + private sealed class FixedChunkBlobBuilder(int size) : BlobBuilder(new byte[size], size); } } diff --git a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index c87d9b4a6f2502..523eab87f10f0b 100644 --- a/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/libraries/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -34,7 +34,6 @@ -