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 3223b1e1a44b61..92bc170efe4792 100644 --- a/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs +++ b/src/libraries/System.Reflection.Metadata/ref/System.Reflection.Metadata.cs @@ -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) { } @@ -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 ToImmutableArray() { throw null; } @@ -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 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) { } @@ -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? 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; } @@ -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) { } @@ -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.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/Resources/Strings.resx b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx index b832902510c241..7a4c5389d2bfac 100644 --- a/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx +++ b/src/libraries/System.Reflection.Metadata/src/Resources/Strings.resx @@ -438,4 +438,7 @@ '{0}' is not a simple TypeName. + + Array passed to BlobBuilder must be at least 16 bytes long. + \ No newline at end of file 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 56d5dacd85d352..9154d54123d087 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 @@ -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 { @@ -123,148 +128,135 @@ public static void WriteGuid(this byte[] buffer, int start, Guid value) #endif } - 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 < 0x7FF) { - 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 (IsSurrogateChar(c)) + else { - // surrogate pair - if (IsHighSurrogateChar(c) && charPtr < strEnd && IsLowSurrogateChar(*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) && src - srcEnd < 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); - } - } - 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 unsafe int GetUTF8ByteCount(char* str, int charCount, int byteLimit, out char* remainder) +#if !NET + internal static unsafe int GetByteCount(this Encoding encoding, ReadOnlySpan 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) diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs index b8495aa9fcc079..336278d0700189 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs @@ -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 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 - /// /// 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. 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 97967bba080068..6011dd763850da 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,109 @@ 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 bytes long. + /// is negative. + 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)); + } + if (buffer.Length < MinChunkSize) + { + Throw.InvalidArgument(SR.BuilderBufferTooSmall, nameof(buffer)); + } + + _nextOrPrevious = this; + _buffer = buffer; + _maxChunkSize = maxChunkSize; + } + + /// + /// The byte array underpinning the . + /// + /// + /// This can only be called on the head of a chain of instances. + /// Calling the setter will reset the 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(); + } + + _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 current . + /// + public int Capacity + { + get + { + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + return _previousLengthOrFrozenSuffixLengthDelta + _buffer.Length; + } + set + { + if (!IsHead) + { + Throw.InvalidOperationBuilderAlreadyLinked(); + } + if (value < Length) + { + Throw.ArgumentOutOfRange(nameof(value)); + } + if (value != _previousLengthOrFrozenSuffixLengthDelta + _buffer.Length) + { + SetCapacity(value); + } + } } protected virtual BlobBuilder AllocateChunk(int minimalSize) @@ -73,6 +182,37 @@ 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. + /// + protected virtual void OnLinking(BlobBuilder other) + { + // nop + } + + /// + /// Changes the size of the byte array underpinning the . + /// Derived types can override this method to control the allocation strategy. + /// + /// The array's new size. + /// + protected virtual void SetCapacity(int capacity) + { + Array.Resize(ref _buffer, Math.Max(MinChunkSize, capacity)); + } + public void Clear() { if (!IsHead) @@ -399,6 +539,8 @@ public void LinkPrefix(BlobBuilder prefix) return; } + OnLinking(this, prefix); + PreviousLength += prefix.Count; // prefix is not a head anymore: @@ -460,6 +602,8 @@ public void LinkSuffix(BlobBuilder suffix) return; } + OnLinking(this, suffix); + bool isEmpty = Count == 0; // swap buffers of the heads: @@ -519,6 +663,23 @@ 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); + } + [MethodImpl(MethodImplOptions.NoInlining)] private void Expand(int newLength) { @@ -538,6 +699,7 @@ private void Expand(int newLength) throw new InvalidOperationException(SR.Format(SR.ReturnedBuilderSizeTooSmall, GetType(), nameof(AllocateChunk))); } + OnLinking(this, newChunk); var newBuffer = newChunk._buffer; if (_length == 0) @@ -589,6 +751,7 @@ public Blob ReserveBytes(int byteCount) } int start = ReserveBytesImpl(byteCount); + Array.Clear(_buffer, start, byteCount); return new Blob(_buffer, start, byteCount); } @@ -631,18 +794,13 @@ 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; } } @@ -671,19 +829,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); } } @@ -708,31 +860,22 @@ 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; } /// is null. @@ -789,7 +932,8 @@ public void WriteBytes(byte[] buffer, int start, int byteCount) WriteBytes(buffer.AsSpan(start, byteCount)); } - internal void WriteBytes(ReadOnlySpan buffer) + /// Builder is not writable, it has been linked with another one. + public void WriteBytes(ReadOnlySpan buffer) { if (!IsHead) { @@ -1027,7 +1171,7 @@ public void WriteSerializedString(string? value) return; } - WriteUTF8(value, 0, value.Length, allowUnpairedSurrogates: true, prependSize: true); + WriteUTF8(value.AsSpan(), allowUnpairedSurrogates: true, prependSize: true); } /// @@ -1069,48 +1213,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 4be040305ad989..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 @@ -6,6 +6,7 @@ using System.IO; using System.Reflection.Internal; using System.Runtime.InteropServices; +using System.Text; namespace System.Reflection.Metadata { @@ -424,7 +425,7 @@ public void WriteSerializedString(string? str) return; } - WriteUTF8(str, 0, str.Length, allowUnpairedSurrogates: true, prependSize: true); + WriteUTF8(str, allowUnpairedSurrogates: true, prependSize: true); } /// @@ -461,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 = BlobUtilities.GetUTF8ByteCount(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 16a2007781507e..9efaa6a4dbd6c9 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,37 +3,19 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.ComponentModel; using System.Reflection.Internal; using System.Runtime.InteropServices; +using System.Text; 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 @@ -48,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. @@ -72,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) @@ -109,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(16); // 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. @@ -155,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: @@ -163,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: @@ -334,7 +362,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) @@ -555,10 +583,10 @@ private static ImmutableArray SerializeStringHeap( int position = stringHeapStartOffset + heapBuilder.Count; // It is important to use ordinal comparison otherwise we'll use the current culture! - if (prev.EndsWith(entry.Key, StringComparison.Ordinal) && !BlobUtilities.IsLowSurrogateChar(entry.Key[0])) + if (prev.EndsWith(entry.Key, StringComparison.Ordinal) && !char.IsLowSurrogate(entry.Key[0])) { // Map over the tail of prev string. Watch for null-terminator of prev string. - stringVirtualIndexToHeapOffsetMap[entry.Value.GetWriterVirtualIndex()] = position - (BlobUtilities.GetUTF8ByteCount(entry.Key) + 1); + stringVirtualIndexToHeapOffsetMap[entry.Value.GetWriterVirtualIndex()] = position - (Encoding.UTF8.GetByteCount(entry.Key) + 1); } else { 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 c0c576cf806021..e1e3fb0e1e3294 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/Ecma335/MetadataRootBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataRootBuilder.cs index 2945d14acbfb52..56a0912522f987 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataRootBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/MetadataRootBuilder.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Diagnostics; +using System.Text; namespace System.Reflection.Metadata.Ecma335 { @@ -58,8 +59,8 @@ public MetadataRootBuilder(MetadataBuilder tablesAndHeaps, string? metadataVersi Throw.ArgumentNull(nameof(tablesAndHeaps)); } - Debug.Assert(BlobUtilities.GetUTF8ByteCount(DefaultMetadataVersionString) == DefaultMetadataVersionString.Length); - int metadataVersionByteCount = metadataVersion != null ? BlobUtilities.GetUTF8ByteCount(metadataVersion) : DefaultMetadataVersionString.Length; + metadataVersion ??= DefaultMetadataVersionString; + int metadataVersionByteCount = Encoding.UTF8.GetByteCount(metadataVersion); if (metadataVersionByteCount > MetadataSizes.MaxMetadataVersionByteCount) { @@ -67,7 +68,7 @@ public MetadataRootBuilder(MetadataBuilder tablesAndHeaps, string? metadataVersi } _tablesAndHeaps = tablesAndHeaps; - MetadataVersion = metadataVersion ?? DefaultMetadataVersionString; + MetadataVersion = metadataVersion; SuppressValidation = suppressValidation; _serializedMetadata = tablesAndHeaps.GetSerializedMetadata(EmptyRowCounts, metadataVersionByteCount, isStandaloneDebugMetadata: false); } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs index dfd862daee2340..28a138c247b286 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/PortablePdbBuilder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Text; namespace System.Reflection.Metadata.Ecma335 { @@ -60,7 +61,7 @@ public PortablePdbBuilder( _builder = tablesAndHeaps; _entryPoint = entryPoint; - Debug.Assert(BlobUtilities.GetUTF8ByteCount(MetadataVersion) == MetadataVersion.Length); + Debug.Assert(Encoding.UTF8.GetByteCount(MetadataVersion) == MetadataVersion.Length); _serializedMetadata = tablesAndHeaps.GetSerializedMetadata(typeSystemRowCounts, MetadataVersion.Length, isStandaloneDebugMetadata: true); IdProvider = idProvider ?? BlobContentId.GetTimeBasedProvider(); 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..dbdabdd0232537 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 @@ -91,6 +91,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 +128,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 +152,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 +194,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 +203,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/src/System/Reflection/PortableExecutable/PEBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEBuilder.cs index c1461437ea2d74..8389ff7359c1b0 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEBuilder.cs @@ -150,23 +150,16 @@ private ImmutableArray SerializeSections() return result.MoveToImmutable(); } - private static unsafe void WritePESignature(BlobBuilder builder) + private static void WritePESignature(BlobBuilder builder) { // MS-DOS stub (128 bytes) - ReadOnlySpan header = DosHeader; - Debug.Assert(DosHeader.Length == DosHeaderSize); - fixed (byte* ptr = header) - { - builder.WriteBytes(ptr, header.Length); - } + builder.WriteBytes(DosHeader); // PE Signature "PE\0\0" builder.WriteUInt32(PEHeaders.PESignature); } - internal const int DosHeaderSize = 0x80; - - private static ReadOnlySpan DosHeader => // DosHeaderSize + internal static ReadOnlySpan DosHeader => [ 0x4d, 0x5a, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, @@ -275,9 +268,8 @@ private void WritePEHeader(BlobBuilder builder, PEDirectoriesBuilder directories builder.WriteUInt32((uint)BitArithmetic.Align(Header.ComputeSizeOfPEHeaders(sections.Length), Header.FileAlignment)); // Checksum: - // Shall be zero for strong name signing. + // Shall be zero for strong name signing, already zeroed by ReserveBytes. _lazyChecksum = builder.ReserveBytes(sizeof(uint)); - new BlobWriter(_lazyChecksum).WriteUInt32(0); builder.WriteUInt16((ushort)Header.Subsystem); builder.WriteUInt16((ushort)Header.DllCharacteristics); diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEHeaderBuilder.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEHeaderBuilder.cs index 9ddb82d35aa4e3..889956c3f9c7e9 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEHeaderBuilder.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEHeaderBuilder.cs @@ -108,7 +108,7 @@ public static PEHeaderBuilder CreateLibraryHeader() internal bool Is32Bit => Machine != Machine.Amd64 && Machine != Machine.IA64 && Machine != Machine.Arm64 && Machine != Machine.LoongArch64 && Machine != Machine.RiscV64; internal int ComputeSizeOfPEHeaders(int sectionCount) => - PEBuilder.DosHeaderSize + + PEBuilder.DosHeader.Length + PEHeaders.PESignatureSize + CoffHeader.Size + PEHeader.Size(Is32Bit) + diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.EmbeddedPortablePdb.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.EmbeddedPortablePdb.cs index cf8aeb00bcc0da..216ad72d789c7e 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.EmbeddedPortablePdb.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/PortableExecutable/PEReader.EmbeddedPortablePdb.cs @@ -100,7 +100,7 @@ internal static unsafe NativeHeapMemoryBlock DecodeEmbeddedPortablePdbDebugDirec try { #if NET - actualLength = deflate.TryReadAll(new Span(decompressed.Pointer, decompressed.Size)); + actualLength = deflate.ReadAtLeast(new Span(decompressed.Pointer, decompressed.Size), decompressed.Size, throwOnEndOfStream: false); #else using var decompressedStream = new UnmanagedMemoryStream(decompressed.Pointer, decompressed.Size, decompressed.Size, FileAccess.Write); deflate.CopyTo(decompressedStream); diff --git a/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs b/src/libraries/System.Reflection.Metadata/tests/Metadata/BlobTests.cs index a04d2b6cf19ef5..8c83ad2ddc8f3b 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,28 @@ 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[BlobBuilder.MinChunkSize - 1])); + Assert.Throws(() => new BlobBuilderWithEvents(new byte[1024], BlobBuilder.MinChunkSize - 1)); + Assert.Throws(() => new BlobBuilderWithEvents(new byte[1024], -1)); } [Fact] @@ -53,69 +62,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 +134,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 +169,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()) @@ -645,6 +652,16 @@ public void ReserveBytes2() }, blobs[0].GetBytes().ToArray()); } + [Fact] + public void ReserveBytes3() + { + var builder = new BlobBuilder(16); + builder.Buffer.AsSpan().Fill(0xff); + // Reserved buffers must be zero-initialized. + var reserved = builder.ReserveBytes(4); + AssertEx.Equal(Enumerable.Repeat((byte)0, 4).ToArray(), reserved.GetBytes().ToArray()); + } + // TODO: // WriteBytes(byte*) // WriteBytes(stream) @@ -972,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() { @@ -1116,5 +1096,108 @@ 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 SetCapacityGetsCalled() + { + var b = new BlobBuilderWithEvents(); + bool called = false; + b.SettingCapacity += SettingCapacityHandler; + + b.Capacity = 1024; + Assert.True(called); + + void SettingCapacityHandler(int c) + { + Assert.Equal(1024, c); + Assert.False(called); + called = true; + } + } + + [Fact] + public void Chunking() + { + const int ChunkSize = 128; + const byte TestValue = (byte)'a'; + const int TestSize = 1024; + + var b = new FixedChunkBlobBuilder(ChunkSize); + 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 event Action? SettingCapacity; + + public BlobBuilderWithEvents() { } + + public BlobBuilderWithEvents(byte[] bytes, int maxChunkSize = 0) : base(bytes, maxChunkSize) { } + + protected override void OnLinking(BlobBuilder builder) + { + Linking?.Invoke(builder); + base.OnLinking(builder); + } + + protected override void SetCapacity(int capacity) + { + SettingCapacity?.Invoke(capacity); + base.SetCapacity(capacity); + } + } } } 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 18d037049bdc1e..19519125705df6 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 @@ -32,7 +32,6 @@ -