diff --git a/src/System.Reflection.Metadata/src/Resources/Strings.resx b/src/System.Reflection.Metadata/src/Resources/Strings.resx index 00e7aaac3a64..2e505382926b 100644 --- a/src/System.Reflection.Metadata/src/Resources/Strings.resx +++ b/src/System.Reflection.Metadata/src/Resources/Strings.resx @@ -333,6 +333,9 @@ There are too many subnamespaces. + + There are too many exception regions. + Sequence point value is out of range. @@ -393,8 +396,8 @@ Specified label doesn't belong to the current builder. - - Can't emit a branch, the current encoder not created with a branch builder. + + Can't emit a branch or exception region, the current encoder not created with a control flow builder. Base reader must be a full metadata reader. @@ -456,4 +459,16 @@ Unsupported format version: {0} + + The distance between the instruction {0} (offset {1}) and the target label doesn't fit the operand size: {2} + + + Label {0} has not been marked. + + + Method body was created with no exception regions. + + + Invalid exception region bounds: start offset ({0}) is greater than end offset ({1}). + diff --git a/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj b/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj index baff39d1dd0b..7e88802477c3 100644 --- a/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj +++ b/src/System.Reflection.Metadata/src/System.Reflection.Metadata.csproj @@ -39,7 +39,7 @@ - + @@ -61,7 +61,7 @@ - + @@ -70,7 +70,6 @@ - diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BlobEncoders.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BlobEncoders.cs index 5be55bbccbb1..e070659a1a99 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BlobEncoders.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BlobEncoders.cs @@ -347,7 +347,7 @@ public PermissionSetEncoder AddPermission(string typeName, ImmutableArray Builder.WriteSerializedString(typeName); Builder.WriteCompressedInteger(encodedArguments.Length); Builder.WriteBytes(encodedArguments); - return new PermissionSetEncoder(Builder); + return this; } public PermissionSetEncoder AddPermission(string typeName, BlobBuilder encodedArguments) @@ -370,7 +370,7 @@ public PermissionSetEncoder AddPermission(string typeName, BlobBuilder encodedAr Builder.WriteSerializedString(typeName); Builder.WriteCompressedInteger(encodedArguments.Count); encodedArguments.WriteContentTo(Builder); - return new PermissionSetEncoder(Builder); + return this; } } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BranchBuilder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BranchBuilder.cs deleted file mode 100644 index 05c41a72502e..000000000000 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/BranchBuilder.cs +++ /dev/null @@ -1,168 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; - -namespace System.Reflection.Metadata.Ecma335 -{ - public sealed class BranchBuilder - { - // internal for testing: - internal struct BranchInfo - { - internal readonly int ILOffset; - internal readonly LabelHandle Label; - internal readonly byte ShortOpCode; - - internal BranchInfo(int ilOffset, LabelHandle label, byte shortOpCode) - { - ILOffset = ilOffset; - Label = label; - ShortOpCode = shortOpCode; - } - - internal bool IsShortBranchDistance(ImmutableArray.Builder labels, out int distance) - { - const int shortBranchSize = 2; - const int longBranchSize = 5; - - int labelTargetOffset = labels[Label.Id - 1]; - - distance = labelTargetOffset - (ILOffset + shortBranchSize); - if (unchecked((sbyte)distance) == distance) - { - return true; - } - - distance = labelTargetOffset - (ILOffset + longBranchSize); - return false; - } - } - - private readonly ImmutableArray.Builder _branches; - private readonly ImmutableArray.Builder _labels; - - public BranchBuilder() - { - _branches = ImmutableArray.CreateBuilder(); - _labels = ImmutableArray.CreateBuilder(); - } - - internal void Clear() - { - _branches.Clear(); - _labels.Clear(); - } - - internal LabelHandle AddLabel() - { - _labels.Add(-1); - return new LabelHandle(_labels.Count); - } - - internal void AddBranch(int ilOffset, LabelHandle label, byte shortOpCode) - { - Debug.Assert(ilOffset >= 0); - Debug.Assert(_branches.Count == 0 || ilOffset > _branches.Last().ILOffset); - ValidateLabel(label); - _branches.Add(new BranchInfo(ilOffset, label, shortOpCode)); - } - - internal void MarkLabel(int ilOffset, LabelHandle label) - { - Debug.Assert(ilOffset >= 0); - ValidateLabel(label); - _labels[label.Id - 1] = ilOffset; - } - - private void ValidateLabel(LabelHandle label) - { - if (label.IsNil) - { - Throw.ArgumentNull(nameof(label)); - } - - if (label.Id > _labels.Count) - { - Throw.LabelDoesntBelongToBuilder(nameof(label)); - } - } - - // internal for testing: - internal IEnumerable Branches => _branches; - - // internal for testing: - internal IEnumerable Labels => _labels; - - internal int BranchCount => _branches.Count; - - internal void FixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder) - { - int srcOffset = 0; - var branch = _branches[0]; - int branchIndex = 0; - int blobOffset = 0; - foreach (Blob blob in srcBuilder.GetBlobs()) - { - Debug.Assert(blobOffset == 0 || blobOffset == 1 && blob.Buffer[blobOffset - 1] == 0xff); - - while (true) - { - // copy bytes preceding the next branch, or till the end of the blob: - int chunkSize = Math.Min(branch.ILOffset - srcOffset, blob.Length - blobOffset); - dstBuilder.WriteBytes(blob.Buffer, blobOffset, chunkSize); - srcOffset += chunkSize; - blobOffset += chunkSize; - - // there is no branch left in the blob: - if (blobOffset == blob.Length) - { - blobOffset = 0; - break; - } - - Debug.Assert(blob.Buffer[blobOffset] == branch.ShortOpCode && (blobOffset + 1 == blob.Length || blob.Buffer[blobOffset + 1] == 0xff)); - srcOffset += sizeof(byte) + sizeof(sbyte); - - // write actual branch instruction: - int branchDistance; - if (branch.IsShortBranchDistance(_labels, out branchDistance)) - { - dstBuilder.WriteByte(branch.ShortOpCode); - dstBuilder.WriteSByte((sbyte)branchDistance); - } - else - { - dstBuilder.WriteByte((byte)((ILOpCode)branch.ShortOpCode).GetLongBranch()); - dstBuilder.WriteInt32(branchDistance); - } - - // next branch: - branchIndex++; - if (branchIndex == _branches.Count) - { - branch = new BranchInfo(int.MaxValue, default(LabelHandle), 0); - } - else - { - branch = _branches[branchIndex]; - } - - // the branch starts at the very end and its operand is in the next blob: - if (blobOffset == blob.Length - 1) - { - blobOffset = 1; - break; - } - - // skip fake branch instruction: - blobOffset += sizeof(byte) + sizeof(sbyte); - } - } - } - } -} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs new file mode 100644 index 000000000000..2a662d117086 --- /dev/null +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs @@ -0,0 +1,406 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace System.Reflection.Metadata.Ecma335 +{ + public sealed class ControlFlowBuilder + { + // internal for testing: + internal struct BranchInfo + { + internal readonly int ILOffset; + internal readonly LabelHandle Label; + private readonly byte _opCode; + + internal ILOpCode OpCode => (ILOpCode)_opCode; + + internal BranchInfo(int ilOffset, LabelHandle label, ILOpCode opCode) + { + ILOffset = ilOffset; + Label = label; + _opCode = (byte)opCode; + } + + internal bool IsShortBranchDistance(ImmutableArray.Builder labels, out int distance) + { + const int shortBranchSize = 2; + const int longBranchSize = 5; + + int labelTargetOffset = labels[Label.Id - 1]; + if (labelTargetOffset < 0) + { + Throw.InvalidOperation_LabelNotMarked(Label.Id); + } + + distance = labelTargetOffset - (ILOffset + shortBranchSize); + if (unchecked((sbyte)distance) == distance) + { + return true; + } + + distance = labelTargetOffset - (ILOffset + longBranchSize); + return false; + } + } + + internal struct ExceptionHandlerInfo + { + public readonly ExceptionRegionKind Kind; + public readonly LabelHandle TryStart, TryEnd, HandlerStart, HandlerEnd, FilterStart; + public readonly EntityHandle CatchType; + + public ExceptionHandlerInfo( + ExceptionRegionKind kind, + LabelHandle tryStart, + LabelHandle tryEnd, + LabelHandle handlerStart, + LabelHandle handlerEnd, + LabelHandle filterStart, + EntityHandle catchType) + { + Kind = kind; + TryStart = tryStart; + TryEnd = tryEnd; + HandlerStart = handlerStart; + HandlerEnd = handlerEnd; + FilterStart = filterStart; + CatchType = catchType; + } + } + + private readonly ImmutableArray.Builder _branches; + private readonly ImmutableArray.Builder _labels; + private ImmutableArray.Builder _lazyExceptionHandlers; + + public ControlFlowBuilder() + { + _branches = ImmutableArray.CreateBuilder(); + _labels = ImmutableArray.CreateBuilder(); + } + + internal void Clear() + { + _branches.Clear(); + _labels.Clear(); + _lazyExceptionHandlers?.Clear(); + } + + internal LabelHandle AddLabel() + { + _labels.Add(-1); + return new LabelHandle(_labels.Count); + } + + internal void AddBranch(int ilOffset, LabelHandle label, ILOpCode opCode) + { + Debug.Assert(ilOffset >= 0); + Debug.Assert(_branches.Count == 0 || ilOffset > _branches.Last().ILOffset); + ValidateLabel(label, nameof(label)); + _branches.Add(new BranchInfo(ilOffset, label, opCode)); + } + + internal void MarkLabel(int ilOffset, LabelHandle label) + { + Debug.Assert(ilOffset >= 0); + ValidateLabel(label, nameof(label)); + _labels[label.Id - 1] = ilOffset; + } + + private int GetLabelOffsetChecked(LabelHandle label) + { + int offset = _labels[label.Id - 1]; + if (offset < 0) + { + Throw.InvalidOperation_LabelNotMarked(label.Id); + } + + return offset; + } + + private void ValidateLabel(LabelHandle label, string parameterName) + { + if (label.IsNil) + { + Throw.ArgumentNull(parameterName); + } + + if (label.Id > _labels.Count) + { + Throw.LabelDoesntBelongToBuilder(parameterName); + } + } + + /// + /// Adds finally region. + /// + /// Label marking the first instruction of the try block. + /// Label marking the instruction immediately following the try block. + /// Label marking the first instruction of the handler. + /// Label marking the instruction immediately following the handler. + /// Encoder for the next clause. + /// A label was not defined by an instruction encoder this builder is associated with. + /// A label has default value. + public void AddFinallyRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) => + AddExceptionRegion(ExceptionRegionKind.Finally, tryStart, tryEnd, handlerStart, handlerEnd); + + /// + /// Adds fault region. + /// + /// Label marking the first instruction of the try block. + /// Label marking the instruction immediately following the try block. + /// Label marking the first instruction of the handler. + /// Label marking the instruction immediately following the handler. + /// A label was not defined by an instruction encoder this builder is associated with. + /// A label has default value. + public void AddFaultRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) => + AddExceptionRegion(ExceptionRegionKind.Fault, tryStart, tryEnd, handlerStart, handlerEnd); + + /// + /// Adds catch region. + /// + /// Label marking the first instruction of the try block. + /// Label marking the instruction immediately following the try block. + /// Label marking the first instruction of the handler. + /// Label marking the instruction immediately following the handler. + /// The type of exception to be caught: , or . + /// A label was not defined by an instruction encoder this builder is associated with. + /// is not a valid type handle. + /// A label has default value. + public void AddCatchRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, EntityHandle catchType) + { + if (!ExceptionRegionEncoder.IsValidCatchTypeHandle(catchType)) + { + Throw.InvalidArgument_Handle(nameof(catchType)); + } + + AddExceptionRegion(ExceptionRegionKind.Catch, tryStart, tryEnd, handlerStart, handlerEnd, catchType: catchType); + } + + /// + /// Adds catch region. + /// + /// Label marking the first instruction of the try block. + /// Label marking the instruction immediately following the try block. + /// Label marking the first instruction of the handler. + /// Label marking the instruction immediately following the handler. + /// Label marking the first instruction of the filter block. + /// A label was not defined by an instruction encoder this builder is associated with. + /// A label has default value. + public void AddFilterRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, LabelHandle filterStart) + { + ValidateLabel(filterStart, nameof(filterStart)); + AddExceptionRegion(ExceptionRegionKind.Filter, tryStart, tryEnd, handlerStart, handlerEnd, filterStart: filterStart); + } + + private void AddExceptionRegion( + ExceptionRegionKind kind, + LabelHandle tryStart, + LabelHandle tryEnd, + LabelHandle handlerStart, + LabelHandle handlerEnd, + LabelHandle filterStart = default(LabelHandle), + EntityHandle catchType = default(EntityHandle)) + { + ValidateLabel(tryStart, nameof(tryStart)); + ValidateLabel(tryEnd, nameof(tryEnd)); + ValidateLabel(handlerStart, nameof(handlerStart)); + ValidateLabel(handlerEnd, nameof(handlerEnd)); + + if (_lazyExceptionHandlers == null) + { + _lazyExceptionHandlers = ImmutableArray.CreateBuilder(); + } + + _lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filterStart, catchType)); + } + + // internal for testing: + internal IEnumerable Branches => _branches; + + // internal for testing: + internal IEnumerable Labels => _labels; + + internal int BranchCount => _branches.Count; + + internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0; + + /// + internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder) + { + var branch = _branches[0]; + int branchIndex = 0; + + // offset within the source builder + int srcOffset = 0; + + // current offset within the current source blob + int srcBlobOffset = 0; + + foreach (Blob srcBlob in srcBuilder.GetBlobs()) + { + Debug.Assert( + srcBlobOffset == 0 || + srcBlobOffset == 1 && srcBlob.Buffer[0] == 0xff || + srcBlobOffset == 4 && srcBlob.Buffer[0] == 0xff && srcBlob.Buffer[1] == 0xff && srcBlob.Buffer[2] == 0xff && srcBlob.Buffer[3] == 0xff); + + while (true) + { + // copy bytes preceding the next branch, or till the end of the blob: + int chunkSize = Math.Min(branch.ILOffset - srcOffset, srcBlob.Length - srcBlobOffset); + dstBuilder.WriteBytes(srcBlob.Buffer, srcBlobOffset, chunkSize); + srcOffset += chunkSize; + srcBlobOffset += chunkSize; + + // there is no branch left in the blob: + if (srcBlobOffset == srcBlob.Length) + { + srcBlobOffset = 0; + break; + } + + Debug.Assert(srcBlob.Buffer[srcBlobOffset] == (byte)branch.OpCode); + + int operandSize = branch.OpCode.GetBranchOperandSize(); + bool isShortInstruction = operandSize == 1; + + // Note: the 4B operand is contiguous since we wrote it via BlobBuilder.WriteInt32() + Debug.Assert( + srcBlobOffset + 1 == srcBlob.Length || + (isShortInstruction ? + srcBlob.Buffer[srcBlobOffset + 1] == 0xff : + BitConverter.ToUInt32(srcBlob.Buffer, srcBlobOffset + 1) == 0xffffffff)); + + // write branch opcode: + dstBuilder.WriteByte(srcBlob.Buffer[srcBlobOffset]); + + // write branch operand: + int branchDistance; + bool isShortDistance = branch.IsShortBranchDistance(_labels, out branchDistance); + + if (isShortInstruction && !isShortDistance) + { + // We could potentially implement algortihm that automatically fixes up the branch instructions as well to accomodate bigger distances, + // however an optimal algorithm would be rather complex (something like: calculate topological ordering of crossing branch instructions + // and then use fixed point to eliminate cycles). If the caller doesn't care about optimal IL size they can use long branches whenever the + // distance is unknown upfront. If they do they probably already implement more sophisticad algorithm for IL layout optimization already. + throw new InvalidOperationException(SR.Format(SR.DistanceBetweenInstructionAndLabelTooBig, branch.OpCode, srcOffset, branchDistance)); + } + + if (isShortInstruction) + { + dstBuilder.WriteSByte((sbyte)branchDistance); + } + else + { + dstBuilder.WriteInt32(branchDistance); + } + + srcOffset += sizeof(byte) + operandSize; + + // next branch: + branchIndex++; + if (branchIndex == _branches.Count) + { + branch = new BranchInfo(int.MaxValue, default(LabelHandle), 0); + } + else + { + branch = _branches[branchIndex]; + } + + // the branch starts at the very end and its operand is in the next blob: + if (srcBlobOffset == srcBlob.Length - 1) + { + srcBlobOffset = operandSize; + break; + } + + // skip fake branch instruction: + srcBlobOffset += sizeof(byte) + operandSize; + } + } + } + + internal void SerializeExceptionTable(BlobBuilder builder) + { + if (_lazyExceptionHandlers == null || _lazyExceptionHandlers.Count == 0) + { + return; + } + + var regionEncoder = ExceptionRegionEncoder.SerializeTableHeader(builder, _lazyExceptionHandlers.Count, HasSmallExceptionRegions()); + + foreach (var handler in _lazyExceptionHandlers) + { + // Note that labels have been validated when added to the handler list, + // they might not have been marked though. + + int tryStart = GetLabelOffsetChecked(handler.TryStart); + int tryEnd = GetLabelOffsetChecked(handler.TryEnd); + int handlerStart = GetLabelOffsetChecked(handler.HandlerStart); + int handlerEnd = GetLabelOffsetChecked(handler.HandlerEnd); + + if (tryStart > tryEnd) + { + Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, tryStart, tryEnd)); + } + + if (handlerStart > handlerEnd) + { + Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, handlerStart, handlerEnd)); + } + + int catchTokenOrOffset; + switch (handler.Kind) + { + case ExceptionRegionKind.Catch: + catchTokenOrOffset = MetadataTokens.GetToken(handler.CatchType); + break; + + case ExceptionRegionKind.Filter: + catchTokenOrOffset = GetLabelOffsetChecked(handler.FilterStart); + break; + + default: + catchTokenOrOffset = 0; + break; + } + + regionEncoder.AddUnchecked( + handler.Kind, + tryStart, + tryEnd - tryStart, + handlerStart, + handlerEnd - handlerStart, + catchTokenOrOffset); + } + } + + private bool HasSmallExceptionRegions() + { + Debug.Assert(_lazyExceptionHandlers != null); + + if (!ExceptionRegionEncoder.IsSmallRegionCount(_lazyExceptionHandlers.Count)) + { + return false; + } + + foreach (var handler in _lazyExceptionHandlers) + { + if (!ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.TryStart), GetLabelOffsetChecked(handler.TryEnd)) || + !ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.HandlerStart), GetLabelOffsetChecked(handler.HandlerEnd))) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ExceptionRegionEncoder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ExceptionRegionEncoder.cs index e60b56277db9..093fb57eb0fe 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ExceptionRegionEncoder.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ExceptionRegionEncoder.cs @@ -2,111 +2,277 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace System.Reflection.Metadata.Ecma335 { public struct ExceptionRegionEncoder { - private readonly int _exceptionRegionCount; - private readonly bool _isSmallFormat; + private const int TableHeaderSize = 4; + + private const int SmallRegionSize = + sizeof(short) + // Flags + sizeof(short) + // TryOffset + sizeof(byte) + // TryLength + sizeof(short) + // HandlerOffset + sizeof(byte) + // HandleLength + sizeof(int); // ClassToken | FilterOffset + + private const int FatRegionSize = + sizeof(int) + // Flags + sizeof(int) + // TryOffset + sizeof(int) + // TryLength + sizeof(int) + // HandlerOffset + sizeof(int) + // HandleLength + sizeof(int); // ClassToken | FilterOffset + + private const int ThreeBytesMaxValue = 0xffffff; + internal const int MaxSmallExceptionRegions = (byte.MaxValue - TableHeaderSize) / SmallRegionSize; + internal const int MaxExceptionRegions = (ThreeBytesMaxValue - TableHeaderSize) / FatRegionSize; + + /// + /// The underlying builder. + /// public BlobBuilder Builder { get; } - internal ExceptionRegionEncoder(BlobBuilder builder, int exceptionRegionCount, bool hasLargeRegions) + /// + /// True if the encoder uses small format. + /// + public bool HasSmallFormat { get; } + + internal ExceptionRegionEncoder(BlobBuilder builder, bool hasSmallFormat) { Builder = builder; - _exceptionRegionCount = exceptionRegionCount; - _isSmallFormat = !hasLargeRegions && IsSmallRegionCount(exceptionRegionCount); + HasSmallFormat = hasSmallFormat; + } + + /// + /// Returns true if the number of exception regions first small format. + /// + /// Number of exception regions. + public static bool IsSmallRegionCount(int exceptionRegionCount) => + unchecked((uint)exceptionRegionCount) <= MaxSmallExceptionRegions; + + /// + /// Returns true if the region fits small format. + /// + /// Start offset of the region. + /// Length of the region. + public static bool IsSmallExceptionRegion(int startOffset, int length) => + unchecked((uint)startOffset) <= ushort.MaxValue && unchecked((uint)length) <= byte.MaxValue; + + internal static bool IsSmallExceptionRegionFromBounds(int startOffset, int endOffset) => + IsSmallExceptionRegion(startOffset, endOffset - startOffset); + + internal static int GetExceptionTableSize(int exceptionRegionCount, bool isSmallFormat) => + TableHeaderSize + exceptionRegionCount * (isSmallFormat ? SmallRegionSize : FatRegionSize); + + internal static bool IsExceptionRegionCountInBounds(int exceptionRegionCount) => + unchecked((uint)exceptionRegionCount) <= MaxExceptionRegions; + + internal static bool IsValidCatchTypeHandle(EntityHandle catchType) + { + return !catchType.IsNil && + (catchType.Kind == HandleKind.TypeDefinition || + catchType.Kind == HandleKind.TypeSpecification || + catchType.Kind == HandleKind.TypeReference); } - public void StartRegions() + internal static ExceptionRegionEncoder SerializeTableHeader(BlobBuilder builder, int exceptionRegionCount, bool hasSmallRegions) { - if (_exceptionRegionCount == 0) - { - return; - } + Debug.Assert(exceptionRegionCount > 0); const byte EHTableFlag = 0x01; const byte FatFormatFlag = 0x40; - int dataSize = GetExceptionTableSize(_exceptionRegionCount, _isSmallFormat); + bool hasSmallFormat = hasSmallRegions && IsSmallRegionCount(exceptionRegionCount); + int dataSize = GetExceptionTableSize(exceptionRegionCount, hasSmallFormat); - Builder.Align(4); - if (_isSmallFormat) + builder.Align(4); + if (hasSmallFormat) { - Builder.WriteByte(EHTableFlag); - Builder.WriteByte((byte)(dataSize & 0xff)); - Builder.WriteInt16(0); + builder.WriteByte(EHTableFlag); + builder.WriteByte(unchecked((byte)dataSize)); + builder.WriteInt16(0); } else { - Builder.WriteByte(EHTableFlag | FatFormatFlag); - Builder.WriteByte((byte)(dataSize & 0xff)); - Builder.WriteUInt16((ushort)((dataSize >> 8) & 0xffff)); + Debug.Assert(dataSize <= 0x00ffffff); + builder.WriteByte(EHTableFlag | FatFormatFlag); + builder.WriteByte(unchecked((byte)dataSize)); + builder.WriteUInt16(unchecked((ushort)(dataSize >> 8))); } - } - public static bool IsSmallRegionCount(int exceptionRegionCount) - { - return GetExceptionTableSize(exceptionRegionCount, isSmallFormat: true) <= 0xff; + return new ExceptionRegionEncoder(builder, hasSmallFormat); } - public static bool IsSmallExceptionRegion(int startOffset, int length) + /// + /// Adds a finally clause. + /// + /// Try block start offset. + /// Try block length. + /// Handler start offset. + /// Handler length. + /// Encoder for the next clause. + /// + /// , , or is out of range. + /// + /// Method body was not declared to have exception regions. + public ExceptionRegionEncoder AddFinally(int tryOffset, int tryLength, int handlerOffset, int handlerLength) { - return startOffset <= 0xffff && length <= 0xff; + return Add(ExceptionRegionKind.Finally, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0); } - internal static int GetExceptionTableSize(int exceptionRegionCount, bool isSmallFormat) + /// + /// Adds a fault clause. + /// + /// Try block start offset. + /// Try block length. + /// Handler start offset. + /// Handler length. + /// Encoder for the next clause. + /// + /// , , or is out of range. + /// + /// Method body was not declared to have exception regions. + public ExceptionRegionEncoder AddFault(int tryOffset, int tryLength, int handlerOffset, int handlerLength) { - const int HeaderSize = 4; - - const int SmallRegionSize = - sizeof(short) + // Flags - sizeof(short) + // TryOffset - sizeof(byte) + // TryLength - sizeof(short) + // HandlerOffset - sizeof(byte) + // HandleLength - sizeof(int); // ClassToken | FilterOffset - - const int FatRegionSize = - sizeof(int) + // Flags - sizeof(int) + // TryOffset - sizeof(int) + // TryLength - sizeof(int) + // HandlerOffset - sizeof(int) + // HandleLength - sizeof(int); // ClassToken | FilterOffset - - return HeaderSize + exceptionRegionCount * (isSmallFormat ? SmallRegionSize : FatRegionSize); + return Add(ExceptionRegionKind.Fault, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0); } - public void AddFinally(int tryOffset, int tryLength, int handlerOffset, int handlerLength) + /// + /// Adds a fault clause. + /// + /// Try block start offset. + /// Try block length. + /// Handler start offset. + /// Handler length. + /// + /// , or . + /// + /// Encoder for the next clause. + /// is invalid. + /// + /// , , or is out of range. + /// + /// Method body was not declared to have exception regions. + public ExceptionRegionEncoder AddCatch(int tryOffset, int tryLength, int handlerOffset, int handlerLength, EntityHandle catchType) { - AddRegion(ExceptionRegionKind.Finally, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0); + return Add(ExceptionRegionKind.Catch, tryOffset, tryLength, handlerOffset, handlerLength, catchType, 0); } - public void AddFault(int tryOffset, int tryLength, int handlerOffset, int handlerLength) + /// + /// Adds a fault clause. + /// + /// Try block start offset. + /// Try block length. + /// Handler start offset. + /// Handler length. + /// Offset of the filter block. + /// Encoder for the next clause. + /// + /// , , or is out of range. + /// + /// Method body was not declared to have exception regions. + public ExceptionRegionEncoder AddFilter(int tryOffset, int tryLength, int handlerOffset, int handlerLength, int filterOffset) { - AddRegion(ExceptionRegionKind.Fault, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), 0); + return Add(ExceptionRegionKind.Filter, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), filterOffset); } - public void AddCatch(int tryOffset, int tryLength, int handlerOffset, int handlerLength, EntityHandle catchType) + /// + /// Adds an exception clause. + /// + /// Clause kind. + /// Try block start offset. + /// Try block length. + /// Handler start offset. + /// Handler length. + /// + /// , or , + /// or nil if is not + /// + /// + /// Offset of the filter block, or 0 if the is not . + /// + /// Encoder for the next clause. + /// is invalid. + /// has invalid value. + /// + /// , , or is out of range. + /// + /// Method body was not declared to have exception regions. + public ExceptionRegionEncoder Add( + ExceptionRegionKind kind, + int tryOffset, + int tryLength, + int handlerOffset, + int handlerLength, + EntityHandle catchType = default(EntityHandle), + int filterOffset = 0) { - AddRegion(ExceptionRegionKind.Catch, tryOffset, tryLength, handlerOffset, handlerLength, catchType, 0); - } + if (Builder == null) + { + Throw.InvalidOperation(SR.MethodHasNoExceptionRegions); + } - public void AddFilter(int tryOffset, int tryLength, int handlerOffset, int handlerLength, int filterOffset) - { - AddRegion(ExceptionRegionKind.Filter, tryOffset, tryLength, handlerOffset, handlerLength, default(EntityHandle), filterOffset); + if (HasSmallFormat) + { + if (unchecked((ushort)tryOffset) != tryOffset) Throw.ArgumentOutOfRange(nameof(tryOffset)); + if (unchecked((byte)tryLength) != tryLength) Throw.ArgumentOutOfRange(nameof(tryLength)); + if (unchecked((ushort)handlerOffset) != handlerOffset) Throw.ArgumentOutOfRange(nameof(handlerOffset)); + if (unchecked((byte)handlerLength) != handlerLength) Throw.ArgumentOutOfRange(nameof(handlerLength)); + } + else + { + if (tryOffset < 0) Throw.ArgumentOutOfRange(nameof(tryOffset)); + if (tryLength < 0) Throw.ArgumentOutOfRange(nameof(tryLength)); + if (handlerOffset < 0) Throw.ArgumentOutOfRange(nameof(handlerOffset)); + if (handlerLength < 0) Throw.ArgumentOutOfRange(nameof(handlerLength)); + } + + int catchTokenOrOffset; + switch (kind) + { + case ExceptionRegionKind.Catch: + if (!IsValidCatchTypeHandle(catchType)) + { + Throw.InvalidArgument_Handle(nameof(catchType)); + } + + catchTokenOrOffset = MetadataTokens.GetToken(catchType); + break; + + case ExceptionRegionKind.Filter: + if (filterOffset < 0) + { + Throw.ArgumentOutOfRange(nameof(filterOffset)); + } + + catchTokenOrOffset = filterOffset; + break; + + case ExceptionRegionKind.Finally: + case ExceptionRegionKind.Fault: + catchTokenOrOffset = 0; + break; + + default: + throw new ArgumentOutOfRangeException(nameof(kind)); + } + + AddUnchecked(kind, tryOffset, tryLength, handlerOffset, handlerLength, catchTokenOrOffset); + return this; } - public void AddRegion( + internal void AddUnchecked( ExceptionRegionKind kind, int tryOffset, int tryLength, int handlerOffset, int handlerLength, - EntityHandle catchType, - int filterOffset) + int catchTokenOrOffset) { - if (_isSmallFormat) + if (HasSmallFormat) { Builder.WriteUInt16((ushort)kind); Builder.WriteUInt16((ushort)tryOffset); @@ -123,24 +289,7 @@ public void AddRegion( Builder.WriteInt32(handlerLength); } - switch (kind) - { - case ExceptionRegionKind.Catch: - Builder.WriteInt32(MetadataTokens.GetToken(catchType)); - break; - - case ExceptionRegionKind.Filter: - Builder.WriteInt32(filterOffset); - break; - - default: - Builder.WriteInt32(0); - break; - } - } - - public void EndRegions() - { + Builder.WriteInt32(catchTokenOrOffset); } } } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs index a81641296ca0..3e63ef1b92c6 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/InstructionEncoder.cs @@ -2,26 +2,61 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Diagnostics; + namespace System.Reflection.Metadata.Ecma335 { + /// + /// Encodes instructions. + /// public struct InstructionEncoder { - public BlobBuilder Builder { get; } - private readonly BranchBuilder _branchBuilderOpt; + /// + /// Underlying builder where encoded instructions are written to. + /// + public BlobBuilder CodeBuilder { get; } + + /// + /// Builder tracking labels, branches and exception handlers. + /// + /// + /// If null the encoder doesn't support constuction of control flow. + /// + public ControlFlowBuilder ControlFlowBuilder { get; } - public InstructionEncoder(BlobBuilder builder, BranchBuilder branchBuilder = null) + /// + /// Creates an encoder backed by code and control-flow builders. + /// + /// Builder to write encoded instructions to. + /// + /// Builder tracking labels, branches and exception handlers. + /// Must be specified to be able to use some of the control-flow factory methods of , + /// such as , , etc. + /// + public InstructionEncoder(BlobBuilder codeBuilder, ControlFlowBuilder controlFlowBuilder = null) { - Builder = builder; - _branchBuilderOpt = branchBuilder; + if (codeBuilder == null) + { + Throw.BuilderArgumentNull(); + } + + CodeBuilder = codeBuilder; + ControlFlowBuilder = controlFlowBuilder; } - public int Offset => Builder.Count; + /// + /// Offset of the next encoded instruction. + /// + public int Offset => CodeBuilder.Count; + /// + /// Encodes specified op-code. + /// public void OpCode(ILOpCode code) { if (unchecked((byte)code) == (ushort)code) { - Builder.WriteByte((byte)code); + CodeBuilder.WriteByte((byte)code); } else { @@ -29,48 +64,90 @@ public void OpCode(ILOpCode code) // the byte stream with the high-order byte first, // in contrast to the little-endian format of the // numeric arguments and tokens. - Builder.WriteUInt16BE((ushort)code); + CodeBuilder.WriteUInt16BE((ushort)code); } } + /// + /// Encodes a token. + /// public void Token(EntityHandle handle) { Token(MetadataTokens.GetToken(handle)); } + /// + /// Encodes a token. + /// public void Token(int token) { - Builder.WriteInt32(token); + CodeBuilder.WriteInt32(token); } - public void LongBranchTarget(int ilOffset) + /// + /// Encodes ldstr instruction and its operand. + /// + public void LoadString(UserStringHandle handle) { - Builder.WriteInt32(ilOffset); + OpCode(ILOpCode.Ldstr); + Token(MetadataTokens.GetToken(handle)); } - public void ShortBranchTarget(byte ilOffset) + /// + /// Encodes call instruction and its operand. + /// + public void Call(EntityHandle methodHandle) { - Builder.WriteByte(ilOffset); + if (methodHandle.Kind != HandleKind.MethodDefinition && + methodHandle.Kind != HandleKind.MethodSpecification && + methodHandle.Kind != HandleKind.MemberReference) + { + Throw.InvalidArgument_Handle(nameof(methodHandle)); + } + + OpCode(ILOpCode.Call); + Token(methodHandle); } - public void LoadString(UserStringHandle handle) + /// + /// Encodes call instruction and its operand. + /// + public void Call(MethodDefinitionHandle methodHandle) { - OpCode(ILOpCode.Ldstr); - Token(MetadataTokens.GetToken(handle)); + OpCode(ILOpCode.Call); + Token(methodHandle); } - public void Call(EntityHandle methodHandle) + /// + /// Encodes call instruction and its operand. + /// + public void Call(MethodSpecificationHandle methodHandle) { OpCode(ILOpCode.Call); Token(methodHandle); } + /// + /// Encodes call instruction and its operand. + /// + public void Call(MemberReferenceHandle methodHandle) + { + OpCode(ILOpCode.Call); + Token(methodHandle); + } + + /// + /// Encodes calli instruction and its operand. + /// public void CallIndirect(StandaloneSignatureHandle signature) { OpCode(ILOpCode.Calli); Token(signature); } + /// + /// Encodes constant load instruction. + /// public void LoadConstantI4(int value) { ILOpCode code; @@ -91,12 +168,12 @@ public void LoadConstantI4(int value) if (unchecked((sbyte)value == value)) { OpCode(ILOpCode.Ldc_i4_s); - Builder.WriteSByte(unchecked((sbyte)value)); + CodeBuilder.WriteSByte((sbyte)value); } else { OpCode(ILOpCode.Ldc_i4); - Builder.WriteInt32(value); + CodeBuilder.WriteInt32(value); } return; @@ -105,24 +182,38 @@ public void LoadConstantI4(int value) OpCode(code); } + /// + /// Encodes constant load instruction. + /// public void LoadConstantI8(long value) { OpCode(ILOpCode.Ldc_i8); - Builder.WriteInt64(value); + CodeBuilder.WriteInt64(value); } + /// + /// Encodes constant load instruction. + /// public void LoadConstantR4(float value) { OpCode(ILOpCode.Ldc_r4); - Builder.WriteSingle(value); + CodeBuilder.WriteSingle(value); } + /// + /// Encodes constant load instruction. + /// public void LoadConstantR8(double value) { OpCode(ILOpCode.Ldc_r8); - Builder.WriteDouble(value); + CodeBuilder.WriteDouble(value); } + /// + /// Encodes local variable load instruction. + /// + /// Index of the local variable slot. + /// is negative. public void LoadLocal(int slotIndex) { switch (slotIndex) @@ -131,21 +222,32 @@ public void LoadLocal(int slotIndex) case 1: OpCode(ILOpCode.Ldloc_1); break; case 2: OpCode(ILOpCode.Ldloc_2); break; case 3: OpCode(ILOpCode.Ldloc_3); break; + default: - if (slotIndex < 0xFF) + if (unchecked((uint)slotIndex) <= byte.MaxValue) { OpCode(ILOpCode.Ldloc_s); - Builder.WriteByte(unchecked((byte)slotIndex)); + CodeBuilder.WriteByte((byte)slotIndex); } - else + else if (slotIndex > 0) { OpCode(ILOpCode.Ldloc); - Builder.WriteInt32(slotIndex); + CodeBuilder.WriteInt32(slotIndex); } + else + { + Throw.ArgumentOutOfRange(nameof(slotIndex)); + } + break; } } + /// + /// Encodes local variable store instruction. + /// + /// Index of the local variable slot. + /// is negative. public void StoreLocal(int slotIndex) { switch (slotIndex) @@ -154,35 +256,55 @@ public void StoreLocal(int slotIndex) case 1: OpCode(ILOpCode.Stloc_1); break; case 2: OpCode(ILOpCode.Stloc_2); break; case 3: OpCode(ILOpCode.Stloc_3); break; + default: - if (slotIndex < 0xFF) + if (unchecked((uint)slotIndex) <= byte.MaxValue) { OpCode(ILOpCode.Stloc_s); - Builder.WriteByte(unchecked((byte)slotIndex)); + CodeBuilder.WriteByte((byte)slotIndex); } - else + else if (slotIndex > 0) { OpCode(ILOpCode.Stloc); - Builder.WriteInt32(slotIndex); + CodeBuilder.WriteInt32(slotIndex); + } + else + { + Throw.ArgumentOutOfRange(nameof(slotIndex)); } + break; } } + /// + /// Encodes local variable address load instruction. + /// + /// Index of the local variable slot. + /// is negative. public void LoadLocalAddress(int slotIndex) { - if (slotIndex < 0xFF) + if (unchecked((uint)slotIndex) <= byte.MaxValue) { OpCode(ILOpCode.Ldloca_s); - Builder.WriteByte(unchecked((byte)slotIndex)); + CodeBuilder.WriteByte((byte)slotIndex); } - else + else if (slotIndex > 0) { OpCode(ILOpCode.Ldloca); - Builder.WriteInt32(slotIndex); + CodeBuilder.WriteInt32(slotIndex); + } + else + { + Throw.ArgumentOutOfRange(nameof(slotIndex)); } } + /// + /// Encodes argument load instruction. + /// + /// Index of the argument. + /// is negative. public void LoadArgument(int argumentIndex) { switch (argumentIndex) @@ -191,81 +313,137 @@ public void LoadArgument(int argumentIndex) case 1: OpCode(ILOpCode.Ldarg_1); break; case 2: OpCode(ILOpCode.Ldarg_2); break; case 3: OpCode(ILOpCode.Ldarg_3); break; + default: - if (argumentIndex < 0xFF) + if (unchecked((uint)argumentIndex) <= byte.MaxValue) { OpCode(ILOpCode.Ldarg_s); - Builder.WriteByte(unchecked((byte)argumentIndex)); + CodeBuilder.WriteByte((byte)argumentIndex); } - else + else if (argumentIndex > 0) { OpCode(ILOpCode.Ldarg); - Builder.WriteInt32(argumentIndex); + CodeBuilder.WriteInt32(argumentIndex); + } + else + { + Throw.ArgumentOutOfRange(nameof(argumentIndex)); } + break; } } + /// + /// Encodes argument address load instruction. + /// + /// Index of the argument. + /// is negative. public void LoadArgumentAddress(int argumentIndex) { - if (argumentIndex < 0xFF) + if (unchecked((uint)argumentIndex) <= byte.MaxValue) { OpCode(ILOpCode.Ldarga_s); - Builder.WriteByte(unchecked((byte)argumentIndex)); + CodeBuilder.WriteByte((byte)argumentIndex); } - else + else if (argumentIndex > 0) { OpCode(ILOpCode.Ldarga); - Builder.WriteInt32(argumentIndex); + CodeBuilder.WriteInt32(argumentIndex); + } + else + { + Throw.ArgumentOutOfRange(nameof(argumentIndex)); } } + /// + /// Encodes argument store instruction. + /// + /// Index of the argument. + /// is negative. public void StoreArgument(int argumentIndex) { - if (argumentIndex < 0xFF) + if (unchecked((uint)argumentIndex) <= byte.MaxValue) { OpCode(ILOpCode.Starg_s); - Builder.WriteByte(unchecked((byte)argumentIndex)); + CodeBuilder.WriteByte((byte)argumentIndex); } - else + else if (argumentIndex > 0) { OpCode(ILOpCode.Starg); - Builder.WriteInt32(argumentIndex); + CodeBuilder.WriteInt32(argumentIndex); + } + else + { + Throw.ArgumentOutOfRange(nameof(argumentIndex)); } } + /// + /// Defines a label that can later be used to mark and refer to a location in the instruction stream. + /// + /// Label handle. + /// is null. public LabelHandle DefineLabel() { return GetBranchBuilder().AddLabel(); } + /// + /// Encodes a branch instruction. + /// + /// Branch instruction to encode. + /// Label of the target location in instruction stream. + /// is not a branch instruction. + /// was not defined by this encoder. + /// is null. + /// has default value. public void Branch(ILOpCode code, LabelHandle label) { // throws if code is not a branch: - ILOpCode shortCode = code.GetShortBranch(); + int size = code.GetBranchOperandSize(); - GetBranchBuilder().AddBranch(Offset, label, (byte)shortCode); - OpCode(shortCode); + GetBranchBuilder().AddBranch(Offset, label, code); + OpCode(code); // -1 points in the middle of the branch instruction and is thus invalid. // We want to produce invalid IL so that if the caller doesn't patch the branches // the branch instructions will be invalid in an obvious way. - Builder.WriteSByte(-1); + if (size == 1) + { + CodeBuilder.WriteSByte(-1); + } + else + { + Debug.Assert(size == 4); + CodeBuilder.WriteInt32(-1); + } } + /// + /// Associates specified label with the current IL offset. + /// + /// Label to mark. + /// + /// A single label may be marked multiple times, the last offset wins. + /// + /// is null. + /// was not defined by this encoder. + /// has default value. public void MarkLabel(LabelHandle label) { GetBranchBuilder().MarkLabel(Offset, label); } - private BranchBuilder GetBranchBuilder() + private ControlFlowBuilder GetBranchBuilder() { - if (_branchBuilderOpt == null) + if (ControlFlowBuilder == null) { - Throw.BranchBuilderNotAvailable(); + Throw.ControlFlowBuilderNotAvailable(); } - return _branchBuilderOpt; + return ControlFlowBuilder; } } } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/LabelHandle.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/LabelHandle.cs index 95a299b3d97d..814dd2ce6665 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/LabelHandle.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/LabelHandle.cs @@ -8,8 +8,10 @@ namespace System.Reflection.Metadata.Ecma335 { public struct LabelHandle : IEquatable { - // 1-based - internal readonly int Id; + /// + /// 1-based id identifying the label within the context of a . + /// + public int Id { get; } internal LabelHandle(int id) { diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodiesEncoder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodiesEncoder.cs deleted file mode 100644 index a36fd1a167e9..000000000000 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodiesEncoder.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.Reflection.Metadata.Ecma335 -{ - public struct MethodBodiesEncoder - { - public BlobBuilder Builder { get; } - - public MethodBodiesEncoder(BlobBuilder builder = null) - { - if (builder == null) - { - builder = new BlobBuilder(); - } - - // Fat methods are 4-byte aligned. We calculate the alignment relative to the start of the ILStream. - // - // See ECMA-335 paragraph 25.4.5, Method data section: - // "At the next 4-byte boundary following the method body can be extra method data sections." - if ((builder.Count % 4) != 0) - { - throw new ArgumentException(SR.BuilderMustAligned, nameof(builder)); - } - - Builder = builder; - } - - public MethodBodyEncoder AddMethodBody( - int maxStack = 8, - int exceptionRegionCount = 0, - StandaloneSignatureHandle localVariablesSignature = default(StandaloneSignatureHandle), - MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals) - { - if (unchecked((ushort)maxStack) > ushort.MaxValue) - { - Throw.ArgumentOutOfRange(nameof(maxStack)); - } - - if (exceptionRegionCount < 0) - { - Throw.ArgumentOutOfRange(nameof(exceptionRegionCount)); - } - - return new MethodBodyEncoder(Builder, (ushort)maxStack, exceptionRegionCount, localVariablesSignature, attributes); - } - } -} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyAttributes.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyAttributes.cs index e741ee274e49..f30744ec0f7b 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyAttributes.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyAttributes.cs @@ -9,6 +9,5 @@ public enum MethodBodyAttributes { None = 0, InitLocals = 1, - LargeExceptionRegions = 2, } } diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyEncoder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyEncoder.cs deleted file mode 100644 index e5f264856e46..000000000000 --- a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyEncoder.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using System.Diagnostics; - -namespace System.Reflection.Metadata.Ecma335 -{ - public struct MethodBodyEncoder - { - public BlobBuilder Builder { get; } - - private readonly ushort _maxStack; - private readonly int _exceptionRegionCount; - private readonly StandaloneSignatureHandle _localVariablesSignature; - private readonly byte _attributes; - - internal MethodBodyEncoder( - BlobBuilder builder, - ushort maxStack, - int exceptionRegionCount, - StandaloneSignatureHandle localVariablesSignature, - MethodBodyAttributes attributes) - { - Builder = builder; - _maxStack = maxStack; - _localVariablesSignature = localVariablesSignature; - _attributes = (byte)attributes; - _exceptionRegionCount = exceptionRegionCount; - } - - internal bool IsTiny(int codeSize) - { - return codeSize < 64 && _maxStack <= 8 && _localVariablesSignature.IsNil && _exceptionRegionCount == 0; - } - - private int WriteHeader(int codeSize) - { - Blob blob; - return WriteHeader(codeSize, false, out blob); - } - - private int WriteHeader(int codeSize, bool codeSizeFixup, out Blob codeSizeBlob) - { - const int TinyFormat = 2; - const int FatFormat = 3; - const int MoreSections = 8; - const byte InitLocals = 0x10; - - int offset; - - if (IsTiny(codeSize)) - { - offset = Builder.Count; - Builder.WriteByte((byte)((codeSize << 2) | TinyFormat)); - - Debug.Assert(!codeSizeFixup); - codeSizeBlob = default(Blob); - } - else - { - Builder.Align(4); - - offset = Builder.Count; - - ushort flags = (3 << 12) | FatFormat; - if (_exceptionRegionCount > 0) - { - flags |= MoreSections; - } - - if ((_attributes & (int)MethodBodyAttributes.InitLocals) != 0) - { - flags |= InitLocals; - } - - Builder.WriteUInt16((ushort)(_attributes | flags)); - Builder.WriteUInt16(_maxStack); - if (codeSizeFixup) - { - codeSizeBlob = Builder.ReserveBytes(sizeof(int)); - } - else - { - codeSizeBlob = default(Blob); - Builder.WriteInt32(codeSize); - } - - Builder.WriteInt32(_localVariablesSignature.IsNil ? 0 : MetadataTokens.GetToken(_localVariablesSignature)); - } - - return offset; - } - - private ExceptionRegionEncoder CreateExceptionEncoder() - { - return new ExceptionRegionEncoder( - Builder, - _exceptionRegionCount, - hasLargeRegions: (_attributes & (int)MethodBodyAttributes.LargeExceptionRegions) != 0); - } - - public ExceptionRegionEncoder WriteInstructions(ImmutableArray instructions, out int bodyOffset) - { - bodyOffset = WriteHeader(instructions.Length); - Builder.WriteBytes(instructions); - return CreateExceptionEncoder(); - } - - public ExceptionRegionEncoder WriteInstructions(ImmutableArray instructions, out int bodyOffset, out Blob instructionBlob) - { - bodyOffset = WriteHeader(instructions.Length); - instructionBlob = Builder.ReserveBytes(instructions.Length); - new BlobWriter(instructionBlob).WriteBytes(instructions); - return CreateExceptionEncoder(); - } - - public ExceptionRegionEncoder WriteInstructions(BlobBuilder codeBuilder, out int bodyOffset) - { - bodyOffset = WriteHeader(codeBuilder.Count); - codeBuilder.WriteContentTo(Builder); - return CreateExceptionEncoder(); - } - - public ExceptionRegionEncoder WriteInstructions(BlobBuilder codeBuilder, BranchBuilder branchBuilder, out int bodyOffset) - { - if (branchBuilder == null || branchBuilder.BranchCount == 0) - { - return WriteInstructions(codeBuilder, out bodyOffset); - } - - // When emitting branches we emitted short branches. - int initialCodeSize = codeBuilder.Count; - Blob codeSizeFixup; - if (IsTiny(initialCodeSize)) - { - // If the method is tiny so far then all branches have to be short - // (the max distance between any label and a branch instruction is < 64). - bodyOffset = WriteHeader(initialCodeSize); - codeSizeFixup = default(Blob); - } - else - { - // Otherwise, it's fat format and we can fixup the size later on: - bodyOffset = WriteHeader(initialCodeSize, true, out codeSizeFixup); - } - - int codeStartOffset = Builder.Count; - branchBuilder.FixupBranches(codeBuilder, Builder); - if (!codeSizeFixup.IsDefault) - { - new BlobWriter(codeSizeFixup).WriteInt32(Builder.Count - codeStartOffset); - } - else - { - Debug.Assert(initialCodeSize == Builder.Count - codeStartOffset); - } - - return CreateExceptionEncoder(); - } - } -} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs new file mode 100644 index 000000000000..0aa427038d18 --- /dev/null +++ b/src/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/MethodBodyStreamEncoder.cs @@ -0,0 +1,203 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Reflection.Metadata.Ecma335 +{ + /// + /// Encodes method body stream. + /// + public struct MethodBodyStreamEncoder + { + public BlobBuilder Builder { get; } + + public MethodBodyStreamEncoder(BlobBuilder builder) + { + if (builder == null) + { + Throw.BuilderArgumentNull(); + } + + // Fat methods are 4-byte aligned. We calculate the alignment relative to the start of the ILStream. + // + // See ECMA-335 paragraph 25.4.5, Method data section: + // "At the next 4-byte boundary following the method body can be extra method data sections." + if ((builder.Count % 4) != 0) + { + throw new ArgumentException(SR.BuilderMustAligned, nameof(builder)); + } + + Builder = builder; + } + + /// + /// Encodes a method body and adds it to the method body stream. + /// + /// Number of bytes to be reserved for instructions. + /// Max stack. + /// Number of exception regions. + /// True if the exception regions should be encoded in 'small' format. + /// Local variables signature handle. + /// Attributes. + /// The offset of the encoded body within the method body stream. + /// + /// , , or is out of allowed range. + /// + public MethodBody AddMethodBody( + int codeSize, + int maxStack = 8, + int exceptionRegionCount = 0, + bool hasSmallExceptionRegions = true, + StandaloneSignatureHandle localVariablesSignature = default(StandaloneSignatureHandle), + MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals) + { + if (codeSize < 0) + { + Throw.ArgumentOutOfRange(nameof(codeSize)); + } + + if (unchecked((uint)maxStack) > ushort.MaxValue) + { + Throw.ArgumentOutOfRange(nameof(maxStack)); + } + + if (!ExceptionRegionEncoder.IsExceptionRegionCountInBounds(exceptionRegionCount)) + { + Throw.ArgumentOutOfRange(nameof(exceptionRegionCount)); + } + + int bodyOffset = SerializeHeader(codeSize, (ushort)maxStack, exceptionRegionCount, attributes, localVariablesSignature); + var instructions = Builder.ReserveBytes(codeSize); + + var regionEncoder = (exceptionRegionCount > 0) ? + ExceptionRegionEncoder.SerializeTableHeader(Builder, exceptionRegionCount, hasSmallExceptionRegions) : default(ExceptionRegionEncoder); + + return new MethodBody(bodyOffset, instructions, regionEncoder); + } + + public struct MethodBody + { + /// + /// Offset of the encoded method body in method body stream. + /// + public int Offset { get; } + + /// + /// Blob reserved for instructions. + /// + public Blob Instructions { get; } + + /// + /// Use to encode exception regions to the method body. + /// + public ExceptionRegionEncoder ExceptionRegions { get; } + + internal MethodBody(int bodyOffset, Blob instructions, ExceptionRegionEncoder exceptionRegions) + { + Offset = bodyOffset; + Instructions = instructions; + ExceptionRegions = exceptionRegions; + } + } + + /// + /// Encodes a method body and adds it to the method body stream. + /// + /// Instruction encoder. + /// Max stack. + /// Local variables signature handle. + /// Attributes. + /// The offset of the encoded body within the method body stream. + /// has default value. + /// is out of range [0, ]. + /// + /// A label targeted by a branch in the instruction stream has not been marked, + /// or the distance between a branch instruction and the target label is doesn't fit the size of the instruction operand. + /// + public int AddMethodBody( + InstructionEncoder instructionEncoder, + int maxStack = 8, + StandaloneSignatureHandle localVariablesSignature = default(StandaloneSignatureHandle), + MethodBodyAttributes attributes = MethodBodyAttributes.InitLocals) + { + if (unchecked((uint)maxStack) > ushort.MaxValue) + { + Throw.ArgumentOutOfRange(nameof(maxStack)); + } + + // The branch fixup code expects the operands of branch instructions in the code builder to be contiguous. + // That's true when we emit thru InstructionEncoder. Taking it as a parameter instead of separate + // code and flow builder parameters ensures they match each other. + var codeBuilder = instructionEncoder.CodeBuilder; + var flowBuilder = instructionEncoder.ControlFlowBuilder; + + if (codeBuilder == null) + { + Throw.ArgumentNull(nameof(instructionEncoder)); + } + + int exceptionRegionCount = flowBuilder?.ExceptionHandlerCount ?? 0; + if (!ExceptionRegionEncoder.IsExceptionRegionCountInBounds(exceptionRegionCount)) + { + Throw.ArgumentOutOfRange(nameof(instructionEncoder), SR.TooManyExceptionRegions); + } + + int bodyOffset = SerializeHeader(codeBuilder.Count, (ushort)maxStack, exceptionRegionCount, attributes, localVariablesSignature); + + if (flowBuilder?.BranchCount > 0) + { + flowBuilder.CopyCodeAndFixupBranches(codeBuilder, Builder); + } + else + { + codeBuilder.WriteContentTo(Builder); + } + + flowBuilder?.SerializeExceptionTable(Builder); + + return bodyOffset; + } + + private int SerializeHeader(int codeSize, ushort maxStack, int exceptionRegionCount, MethodBodyAttributes attributes, StandaloneSignatureHandle localVariablesSignature) + { + const int TinyFormat = 2; + const int FatFormat = 3; + const int MoreSections = 8; + const byte InitLocals = 0x10; + + int offset; + + bool isTiny = codeSize < 64 && maxStack <= 8 && localVariablesSignature.IsNil && exceptionRegionCount == 0; + + if (isTiny) + { + offset = Builder.Count; + Builder.WriteByte((byte)((codeSize << 2) | TinyFormat)); + } + else + { + Builder.Align(4); + + offset = Builder.Count; + + ushort flags = (3 << 12) | FatFormat; + if (exceptionRegionCount > 0) + { + flags |= MoreSections; + } + + if ((attributes & MethodBodyAttributes.InitLocals) != 0) + { + flags |= InitLocals; + } + + Builder.WriteUInt16((ushort)((int)attributes | flags)); + Builder.WriteUInt16(maxStack); + Builder.WriteInt32(codeSize); + Builder.WriteInt32(localVariablesSignature.IsNil ? 0 : MetadataTokens.GetToken(localVariablesSignature)); + } + + return offset; + } + } +} diff --git a/src/System.Reflection.Metadata/src/System/Reflection/Throw.cs b/src/System.Reflection.Metadata/src/System/Reflection/Throw.cs index 09e5ef97f0a0..e55d9c8314d9 100644 --- a/src/System.Reflection.Metadata/src/System/Reflection/Throw.cs +++ b/src/System.Reflection.Metadata/src/System/Reflection/Throw.cs @@ -55,9 +55,9 @@ internal static void SignatureNotVarArg() } [MethodImpl(MethodImplOptions.NoInlining)] - internal static void BranchBuilderNotAvailable() + internal static void ControlFlowBuilderNotAvailable() { - throw new InvalidOperationException(SR.BranchBuilderNotAvailable); + throw new InvalidOperationException(SR.ControlFlowBuilderNotAvailable); } [MethodImpl(MethodImplOptions.NoInlining)] @@ -72,6 +72,12 @@ internal static void InvalidOperation(string message) throw new InvalidOperationException(message); } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void InvalidOperation_LabelNotMarked(int id) + { + throw new InvalidOperationException(SR.Format(SR.LabelNotMarked, id)); + } + [MethodImpl(MethodImplOptions.NoInlining)] internal static void LabelDoesntBelongToBuilder(string parameterName) { @@ -126,6 +132,12 @@ internal static void ArgumentOutOfRange(string parameterName) throw new ArgumentOutOfRangeException(parameterName); } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ArgumentOutOfRange(string parameterName, string message) + { + throw new ArgumentOutOfRangeException(parameterName, message); + } + [MethodImpl(MethodImplOptions.NoInlining)] internal static void BlobTooLarge(string parameterName) { diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs new file mode 100644 index 000000000000..4ca7924c15b8 --- /dev/null +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ControlFlowBuilderTests.cs @@ -0,0 +1,262 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection.Metadata.Tests; +using Xunit; + +namespace System.Reflection.Metadata.Ecma335.Tests +{ + public class ControlFlowBuilderTests + { + [Fact] + public void AddFinallyFaultFilterRegions() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var il = new InstructionEncoder(code, flow); + + var l1 = il.DefineLabel(); + var l2 = il.DefineLabel(); + var l3 = il.DefineLabel(); + var l4 = il.DefineLabel(); + var l5 = il.DefineLabel(); + + il.MarkLabel(l1); + Assert.Equal(0, il.Offset); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l2); + Assert.Equal(1, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l3); + Assert.Equal(3, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l4); + Assert.Equal(6, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l5); + Assert.Equal(10, il.Offset); + + flow.AddFaultRegion(l1, l2, l3, l4); + flow.AddFinallyRegion(l1, l2, l3, l4); + flow.AddFilterRegion(l1, l2, l3, l4, l5); + + var builder = new BlobBuilder(); + builder.WriteByte(0xff); + flow.SerializeExceptionTable(builder); + + AssertEx.Equal(new byte[] + { + 0xFF, 0x00, 0x00, 0x00, // padding + 0x01, // flag + (byte)(builder.Count - 4), // size + 0x00, 0x00, // reserved + + 0x04, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x00, 0x00, 0x00, 0x00, // catch type or filter offset + + 0x02, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x00, 0x00, 0x00, 0x00, // catch type or filter offset + + 0x01, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x0A, 0x00, 0x00, 0x00 // catch type or filter offset + }, builder.ToArray()); + } + + [Fact] + public void AddCatchRegions() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var il = new InstructionEncoder(code, flow); + + var l1 = il.DefineLabel(); + var l2 = il.DefineLabel(); + var l3 = il.DefineLabel(); + var l4 = il.DefineLabel(); + var l5 = il.DefineLabel(); + + il.MarkLabel(l1); + Assert.Equal(0, il.Offset); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l2); + Assert.Equal(1, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l3); + Assert.Equal(3, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l4); + Assert.Equal(6, il.Offset); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l5); + Assert.Equal(10, il.Offset); + + flow.AddCatchRegion(l1, l2, l3, l4, MetadataTokens.TypeDefinitionHandle(1)); + flow.AddCatchRegion(l1, l2, l3, l4, MetadataTokens.TypeSpecificationHandle(2)); + flow.AddCatchRegion(l1, l2, l3, l4, MetadataTokens.TypeReferenceHandle(3)); + + var builder = new BlobBuilder(); + flow.SerializeExceptionTable(builder); + + AssertEx.Equal(new byte[] + { + 0x01, // flag + (byte)builder.Count, // size + 0x00, 0x00, // reserved + + 0x00, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x01, 0x00, 0x00, 0x02, // catch type or filter offset + + 0x00, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x02, 0x00, 0x00, 0x1B, // catch type or filter offset + + 0x00, 0x00, // kind + 0x00, 0x00, // try offset + 0x01, // try length + 0x03, 0x00, // handler offset + 0x03, // handler length + + 0x03, 0x00, 0x00, 0x01, // catch type or filter offset + }, builder.ToArray()); + } + + [Fact] + public void AddRegion_Errors1() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var il = new InstructionEncoder(code, flow); + var ilx = new InstructionEncoder(code, new ControlFlowBuilder()); + + var l1 = il.DefineLabel(); + var l2 = il.DefineLabel(); + var l3 = il.DefineLabel(); + var l4 = il.DefineLabel(); + var l5 = il.DefineLabel(); + + ilx.DefineLabel(); + ilx.DefineLabel(); + ilx.DefineLabel(); + ilx.DefineLabel(); + ilx.DefineLabel(); + ilx.DefineLabel(); + var lx = ilx.DefineLabel(); + + il.MarkLabel(l1); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l2); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l3); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l4); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l5); + + Assert.Throws(() => flow.AddCatchRegion(l1, l2, l3, l4, default(TypeDefinitionHandle))); + Assert.Throws(() => flow.AddCatchRegion(l1, l2, l3, l4, MetadataTokens.MethodDefinitionHandle(1))); + + Assert.Throws(() => flow.AddCatchRegion(default(LabelHandle), l2, l3, l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, default(LabelHandle), l3, l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, l2, default(LabelHandle), l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, l2, l3, default(LabelHandle), MetadataTokens.TypeReferenceHandle(1))); + + Assert.Throws(() => flow.AddCatchRegion(lx, l2, l3, l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, lx, l3, l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, l2, lx, l4, MetadataTokens.TypeReferenceHandle(1))); + Assert.Throws(() => flow.AddCatchRegion(l1, l2, l3, lx, MetadataTokens.TypeReferenceHandle(1))); + } + + [Fact] + public void AddRegion_Errors2() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var il = new InstructionEncoder(code, flow); + + var l1 = il.DefineLabel(); + var l2 = il.DefineLabel(); + var l3 = il.DefineLabel(); + var l4 = il.DefineLabel(); + var l5 = il.DefineLabel(); + + il.MarkLabel(l1); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l2); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l3); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l4); + + var streamBuilder = new BlobBuilder(); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + flow.AddFaultRegion(l2, l1, l3, l4); + Assert.Throws(() => encoder.AddMethodBody(il)); + } + + [Fact] + public void AddRegion_Errors3() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var il = new InstructionEncoder(code, flow); + + var l1 = il.DefineLabel(); + var l2 = il.DefineLabel(); + var l3 = il.DefineLabel(); + var l4 = il.DefineLabel(); + var l5 = il.DefineLabel(); + + il.MarkLabel(l1); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l2); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l3); + il.OpCode(ILOpCode.Nop); + il.MarkLabel(l4); + + var streamBuilder = new BlobBuilder(); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + flow.AddFaultRegion(l1, l2, l4, l3); + Assert.Throws(() => encoder.AddMethodBody(il)); + } + } +} diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ExceptionRegionEncoderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ExceptionRegionEncoderTests.cs new file mode 100644 index 000000000000..567b51c140dc --- /dev/null +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/ExceptionRegionEncoderTests.cs @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection.Metadata.Tests; +using Xunit; + +namespace System.Reflection.Metadata.Ecma335.Tests +{ + public class ExceptionRegionEncoderTests + { + [Fact] + public void IsSmallRegionCount() + { + Assert.True(ExceptionRegionEncoder.IsSmallRegionCount(0)); + Assert.True(ExceptionRegionEncoder.IsSmallRegionCount(20)); + Assert.False(ExceptionRegionEncoder.IsSmallRegionCount(-1)); + Assert.False(ExceptionRegionEncoder.IsSmallRegionCount(21)); + Assert.False(ExceptionRegionEncoder.IsSmallRegionCount(int.MinValue)); + Assert.False(ExceptionRegionEncoder.IsSmallRegionCount(int.MaxValue)); + } + + [Fact] + public void IsSmallExceptionRegion() + { + Assert.True(ExceptionRegionEncoder.IsSmallExceptionRegion(0, 0)); + Assert.True(ExceptionRegionEncoder.IsSmallExceptionRegion(ushort.MaxValue, byte.MaxValue)); + + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(ushort.MaxValue + 1, byte.MaxValue)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(ushort.MaxValue, byte.MaxValue + 1)); + + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(-1, 0)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(0, -1)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(int.MinValue, int.MinValue)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(int.MaxValue, int.MaxValue)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(int.MaxValue, int.MinValue)); + Assert.False(ExceptionRegionEncoder.IsSmallExceptionRegion(int.MinValue, int.MaxValue)); + } + + [Fact] + public void SerializeTableHeader() + { + var builder = new BlobBuilder(); + + builder.WriteByte(0xff); + ExceptionRegionEncoder.SerializeTableHeader(builder, ExceptionRegionEncoder.MaxSmallExceptionRegions, hasSmallRegions: true); + AssertEx.Equal(new byte[] + { + 0xff, 0x00, 0x00, 0x00, // padding + 0x01, // flags + 0xf4, // size + 0x00, 0x00 + }, builder.ToArray()); + builder.Clear(); + + builder.WriteByte(0xff); + ExceptionRegionEncoder.SerializeTableHeader(builder, ExceptionRegionEncoder.MaxExceptionRegions, hasSmallRegions: false); + AssertEx.Equal(new byte[] + { + 0xff, 0x00, 0x00, 0x00, // padding + 0x41, // flags + 0xf4, 0xff, 0xff, // size + }, builder.ToArray()); + } + + [Fact] + public void Add_Small() + { + var builder = new BlobBuilder(); + var encoder = new ExceptionRegionEncoder(builder, hasSmallFormat: true); + + encoder.Add(ExceptionRegionKind.Catch, 1, 2, 4, 5, catchType: MetadataTokens.TypeDefinitionHandle(1)); + + AssertEx.Equal(new byte[] + { + 0x00, 0x00, // kind + 0x01, 0x00, // try offset + 0x02, // try length + 0x04, 0x00, // handler offset + 0x05, // handler length + 0x01, 0x00, 0x00, 0x02 // catch type + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Filter, 0xffff, 0xff, 0xffff, 0xff, filterOffset: int.MaxValue); + + AssertEx.Equal(new byte[] + { + 0x01, 0x00, // kind + 0xff, 0xff, // try offset + 0xff, // try length + 0xff, 0xff, // handler offset + 0xff, // handler length + 0xff, 0xff, 0xff, 0x7f // filter offset + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Fault, 0xffff, 0xff, 0xffff, 0xff); + + AssertEx.Equal(new byte[] + { + 0x04, 0x00, // kind + 0xff, 0xff, // try offset + 0xff, // try length + 0xff, 0xff, // handler offset + 0xff, // handler length + 0x00, 0x00, 0x00, 0x00 + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Finally, 0, 0, 0, 0); + + AssertEx.Equal(new byte[] + { + 0x02, 0x00, // kind + 0x00, 0x00, // try offset + 0x00, // try length + 0x00, 0x00, // handler offset + 0x00, // handler length + 0x00, 0x00, 0x00, 0x00 + }, builder.ToArray()); + builder.Clear(); + } + + [Fact] + public void Add_Large() + { + var builder = new BlobBuilder(); + var encoder = new ExceptionRegionEncoder(builder, hasSmallFormat: false); + + encoder.Add(ExceptionRegionKind.Catch, 1, 2, 4, 5, catchType: MetadataTokens.TypeDefinitionHandle(1)); + + AssertEx.Equal(new byte[] + { + 0x00, 0x00, 0x00, 0x00, // kind + 0x01, 0x00, 0x00, 0x00, // try offset + 0x02, 0x00, 0x00, 0x00, // try length + 0x04, 0x00, 0x00, 0x00, // handler offset + 0x05, 0x00, 0x00, 0x00, // handler length + 0x01, 0x00, 0x00, 0x02 // catch type + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Filter, int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue, filterOffset: int.MaxValue); + + AssertEx.Equal(new byte[] + { + 0x01, 0x00, 0x00, 0x00, // kind + 0xff, 0xff, 0xff, 0x7f, // try offset + 0xff, 0xff, 0xff, 0x7f, // try length + 0xff, 0xff, 0xff, 0x7f, // handler offset + 0xff, 0xff, 0xff, 0x7f, // handler length + 0xff, 0xff, 0xff, 0x7f // filter offset + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Fault, int.MaxValue, int.MaxValue, int.MaxValue, int.MaxValue); + + AssertEx.Equal(new byte[] + { + 0x04, 0x00, 0x00, 0x00, // kind + 0xff, 0xff, 0xff, 0x7f, // try offset + 0xff, 0xff, 0xff, 0x7f, // try length + 0xff, 0xff, 0xff, 0x7f, // handler offset + 0xff, 0xff, 0xff, 0x7f, // handler length + 0x00, 0x00, 0x00, 0x00 + }, builder.ToArray()); + builder.Clear(); + + encoder.Add(ExceptionRegionKind.Finally, 0, 0, 0, 0); + + AssertEx.Equal(new byte[] + { + 0x02, 0x00, 0x00, 0x00, // kind + 0x00, 0x00, 0x00, 0x00, // try offset + 0x00, 0x00, 0x00, 0x00, // try length + 0x00, 0x00, 0x00, 0x00, // handler offset + 0x00, 0x00, 0x00, 0x00, // handler length + 0x00, 0x00, 0x00, 0x00 + }, builder.ToArray()); + builder.Clear(); + } + + [Fact] + public void Add_Errors() + { + Assert.Throws(() => default(ExceptionRegionEncoder).Add(ExceptionRegionKind.Fault, 0, 0, 0, 0)); + + var builder = new BlobBuilder(); + var smallEncoder = new ExceptionRegionEncoder(builder, hasSmallFormat: true); + var fatEncoder = new ExceptionRegionEncoder(builder, hasSmallFormat: false); + + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, -1, 2, 4, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, -1, 4, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, 2, -1, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, 2, 4, -1)); + + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 0x10000, 2, 4, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, 0x100, 4, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, 2, 0x10000, 5)); + Assert.Throws(() => smallEncoder.Add(ExceptionRegionKind.Finally, 1, 2, 4, 0x100)); + + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Finally, -1, 2, 4, 5)); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Finally, 1, -1, 4, 5)); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Finally, 1, 2, -1, 5)); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Finally, 1, 2, 4, -1)); + + Assert.Throws(() => fatEncoder.Add((ExceptionRegionKind)5, 1, 2, 4, 5)); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Filter, 1, 2, 4, 5, filterOffset: -1)); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Catch, 1, 2, 4, 5, catchType: default(EntityHandle))); + Assert.Throws(() => fatEncoder.Add(ExceptionRegionKind.Catch, 1, 2, 4, 5, catchType: MetadataTokens.ImportScopeHandle(1))); + } + } +} diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs new file mode 100644 index 000000000000..8e5ac18a45ef --- /dev/null +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/InstructionEncoderTests.cs @@ -0,0 +1,533 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Reflection.Metadata.Tests; +using Xunit; + +namespace System.Reflection.Metadata.Ecma335.Tests +{ + public class InstructionEncoderTests + { + [Fact] + public void Ctor() + { + var code = new BlobBuilder(); + var flow = new ControlFlowBuilder(); + var ie = new InstructionEncoder(code, flow); + Assert.Same(ie.CodeBuilder, code); + Assert.Same(ie.ControlFlowBuilder, flow); + + Assert.Throws(() => new InstructionEncoder(null)); + } + + [Fact] + public void OpCode() + { + var builder = new BlobBuilder(); + builder.WriteByte(0xff); + + var il = new InstructionEncoder(builder); + Assert.Equal(1, il.Offset); + + il.OpCode(ILOpCode.Add); + Assert.Equal(2, il.Offset); + + builder.WriteByte(0xee); + Assert.Equal(3, il.Offset); + + il.OpCode(ILOpCode.Arglist); + Assert.Equal(5, il.Offset); + + builder.WriteByte(0xdd); + Assert.Equal(6, il.Offset); + + il.OpCode(ILOpCode.Readonly); + Assert.Equal(8, il.Offset); + + builder.WriteByte(0xcc); + Assert.Equal(9, il.Offset); + + AssertEx.Equal(new byte[] + { + 0xFF, + 0x58, + 0xEE, + 0xFE, 0x00, + 0xDD, + 0xFE, 0x1E, + 0xCC + }, builder.ToArray()); + } + + [Fact] + public void TokenInstructions() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + Assert.Equal(0, il.Offset); + + il.Token(MetadataTokens.TypeDefinitionHandle(0x123456)); + Assert.Equal(4, il.Offset); + + il.Token(0x7F7E7D7C); + Assert.Equal(8, il.Offset); + + il.LoadString(MetadataTokens.UserStringHandle(0x010203)); + Assert.Equal(13, il.Offset); + + il.Call(MetadataTokens.MethodDefinitionHandle(0xA0A1A2)); + Assert.Equal(18, il.Offset); + + il.Call(MetadataTokens.MethodSpecificationHandle(0xB0B1B2)); + Assert.Equal(23, il.Offset); + + il.Call(MetadataTokens.MemberReferenceHandle(0xC0C1C2)); + Assert.Equal(28, il.Offset); + + il.Call((EntityHandle)MetadataTokens.MethodDefinitionHandle(0xD0D1D2)); + Assert.Equal(33, il.Offset); + + il.Call((EntityHandle)MetadataTokens.MethodSpecificationHandle(0xE0E1E2)); + Assert.Equal(38, il.Offset); + + il.Call((EntityHandle)MetadataTokens.MemberReferenceHandle(0xF0F1F2)); + Assert.Equal(43, il.Offset); + + il.CallIndirect(MetadataTokens.StandaloneSignatureHandle(0x001122)); + Assert.Equal(48, il.Offset); + + AssertEx.Equal(new byte[] + { + 0x56, 0x34, 0x12, 0x02, + 0x7C, 0x7D, 0x7E, 0x7F, + (byte)ILOpCode.Ldstr, 0x03, 0x02, 0x01, 0x70, + (byte)ILOpCode.Call, 0xA2, 0xA1, 0xA0, 0x06, + (byte)ILOpCode.Call, 0xB2, 0xB1, 0xB0, 0x2B, + (byte)ILOpCode.Call, 0xC2, 0xC1, 0xC0, 0x0A, + (byte)ILOpCode.Call, 0xD2, 0xD1, 0xD0, 0x06, + (byte)ILOpCode.Call, 0xE2, 0xE1, 0xE0, 0x2B, + (byte)ILOpCode.Call, 0xF2, 0xF1, 0xF0, 0x0A, + (byte)ILOpCode.Calli, 0x22, 0x11, 0x00, 0x11 + }, builder.ToArray()); + } + + [Fact] + public void LoadConstantI4() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + for (int i = -1; i < 9; i++) + { + il.LoadConstantI4(i); + } + + il.LoadConstantI4(sbyte.MinValue); + il.LoadConstantI4(sbyte.MaxValue); + il.LoadConstantI4(-2); + il.LoadConstantI4(10); + il.LoadConstantI4(int.MinValue); + il.LoadConstantI4(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldc_i4_m1, + (byte)ILOpCode.Ldc_i4_0, + (byte)ILOpCode.Ldc_i4_1, + (byte)ILOpCode.Ldc_i4_2, + (byte)ILOpCode.Ldc_i4_3, + (byte)ILOpCode.Ldc_i4_4, + (byte)ILOpCode.Ldc_i4_5, + (byte)ILOpCode.Ldc_i4_6, + (byte)ILOpCode.Ldc_i4_7, + (byte)ILOpCode.Ldc_i4_8, + (byte)ILOpCode.Ldc_i4_s, 0x80, + (byte)ILOpCode.Ldc_i4_s, 0x7F, + (byte)ILOpCode.Ldc_i4_s, 0xFE, + (byte)ILOpCode.Ldc_i4_s, 0x0A, + (byte)ILOpCode.Ldc_i4, 0x00, 0x00, 0x00, 0x80, + (byte)ILOpCode.Ldc_i4, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + } + + [Fact] + public void LoadConstantI8() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadConstantI8(0); + il.LoadConstantI8(long.MinValue); + il.LoadConstantI8(long.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldc_i8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte)ILOpCode.Ldc_i8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + (byte)ILOpCode.Ldc_i8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + } + + [Fact] + public void LoadConstantR4() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadConstantR4(0.0f); + il.LoadConstantR4(float.MaxValue); + il.LoadConstantR4(float.MinValue); + il.LoadConstantR4(float.NaN); + il.LoadConstantR4(float.NegativeInfinity); + il.LoadConstantR4(float.PositiveInfinity); + il.LoadConstantR4(float.Epsilon); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldc_r4, 0x00, 0x00, 0x00, 0x00, + (byte)ILOpCode.Ldc_r4, 0xFF, 0xFF, 0x7F, 0x7F, + (byte)ILOpCode.Ldc_r4, 0xFF, 0xFF, 0x7F, 0xFF, + (byte)ILOpCode.Ldc_r4, 0x00, 0x00, 0xC0, 0xFF, + (byte)ILOpCode.Ldc_r4, 0x00, 0x00, 0x80, 0xFF, + (byte)ILOpCode.Ldc_r4, 0x00, 0x00, 0x80, 0x7F, + (byte)ILOpCode.Ldc_r4, 0x01, 0x00, 0x00, 0x00 + }, builder.ToArray()); + } + + [Fact] + public void LoadConstantR8() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadConstantR8(0.0); + il.LoadConstantR8(double.MaxValue); + il.LoadConstantR8(double.MinValue); + il.LoadConstantR8(double.NaN); + il.LoadConstantR8(double.NegativeInfinity); + il.LoadConstantR8(double.PositiveInfinity); + il.LoadConstantR8(double.Epsilon); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldc_r8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + (byte)ILOpCode.Ldc_r8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F, + (byte)ILOpCode.Ldc_r8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, + (byte)ILOpCode.Ldc_r8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, + (byte)ILOpCode.Ldc_r8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, + (byte)ILOpCode.Ldc_r8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F, + (byte)ILOpCode.Ldc_r8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, builder.ToArray()); + } + + [Fact] + public void LoadLocal() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadLocal(0); + il.LoadLocal(1); + il.LoadLocal(2); + il.LoadLocal(3); + il.LoadLocal(4); + il.LoadLocal(byte.MaxValue); + il.LoadLocal(byte.MaxValue + 1); + il.LoadLocal(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldloc_0, + (byte)ILOpCode.Ldloc_1, + (byte)ILOpCode.Ldloc_2, + (byte)ILOpCode.Ldloc_3, + (byte)ILOpCode.Ldloc_s, 0x04, + (byte)ILOpCode.Ldloc_s, 0xFF, + 0xFE, 0x0C, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x0C, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.LoadLocal(-1)); + Assert.Throws(() => il.LoadLocal(int.MinValue)); + } + + [Fact] + public void StoreLocal() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.StoreLocal(0); + il.StoreLocal(1); + il.StoreLocal(2); + il.StoreLocal(3); + il.StoreLocal(4); + il.StoreLocal(byte.MaxValue); + il.StoreLocal(byte.MaxValue + 1); + il.StoreLocal(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Stloc_0, + (byte)ILOpCode.Stloc_1, + (byte)ILOpCode.Stloc_2, + (byte)ILOpCode.Stloc_3, + (byte)ILOpCode.Stloc_s, 0x04, + (byte)ILOpCode.Stloc_s, 0xFF, + 0xFE, 0x0E, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x0E, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.StoreLocal(-1)); + Assert.Throws(() => il.StoreLocal(int.MinValue)); + } + + [Fact] + public void LoadLocalAddress() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadLocalAddress(0); + il.LoadLocalAddress(1); + il.LoadLocalAddress(2); + il.LoadLocalAddress(3); + il.LoadLocalAddress(4); + il.LoadLocalAddress(byte.MaxValue); + il.LoadLocalAddress(byte.MaxValue + 1); + il.LoadLocalAddress(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldloca_s, 0x00, + (byte)ILOpCode.Ldloca_s, 0x01, + (byte)ILOpCode.Ldloca_s, 0x02, + (byte)ILOpCode.Ldloca_s, 0x03, + (byte)ILOpCode.Ldloca_s, 0x04, + (byte)ILOpCode.Ldloca_s, 0xFF, + 0xFE, 0x0D, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x0D, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.LoadLocalAddress(-1)); + Assert.Throws(() => il.LoadLocalAddress(int.MinValue)); + } + + [Fact] + public void LoadArgument() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadArgument(0); + il.LoadArgument(1); + il.LoadArgument(2); + il.LoadArgument(3); + il.LoadArgument(4); + il.LoadArgument(byte.MaxValue); + il.LoadArgument(byte.MaxValue + 1); + il.LoadArgument(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldarg_0, + (byte)ILOpCode.Ldarg_1, + (byte)ILOpCode.Ldarg_2, + (byte)ILOpCode.Ldarg_3, + (byte)ILOpCode.Ldarg_s, 0x04, + (byte)ILOpCode.Ldarg_s, 0xFF, + 0xFE, 0x09, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x09, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.LoadArgument(-1)); + Assert.Throws(() => il.LoadArgument(int.MinValue)); + } + + [Fact] + public void LoadArgumentAddress() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.LoadArgumentAddress(0); + il.LoadArgumentAddress(1); + il.LoadArgumentAddress(2); + il.LoadArgumentAddress(3); + il.LoadArgumentAddress(4); + il.LoadArgumentAddress(byte.MaxValue); + il.LoadArgumentAddress(byte.MaxValue + 1); + il.LoadArgumentAddress(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Ldarga_s, 0x00, + (byte)ILOpCode.Ldarga_s, 0x01, + (byte)ILOpCode.Ldarga_s, 0x02, + (byte)ILOpCode.Ldarga_s, 0x03, + (byte)ILOpCode.Ldarga_s, 0x04, + (byte)ILOpCode.Ldarga_s, 0xFF, + 0xFE, 0x0A, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x0A, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.LoadArgumentAddress(-1)); + Assert.Throws(() => il.LoadArgumentAddress(int.MinValue)); + } + + [Fact] + public void StoreArgument() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + + il.StoreArgument(0); + il.StoreArgument(1); + il.StoreArgument(2); + il.StoreArgument(3); + il.StoreArgument(4); + il.StoreArgument(byte.MaxValue); + il.StoreArgument(byte.MaxValue + 1); + il.StoreArgument(int.MaxValue); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Starg_s, 0x00, + (byte)ILOpCode.Starg_s, 0x01, + (byte)ILOpCode.Starg_s, 0x02, + (byte)ILOpCode.Starg_s, 0x03, + (byte)ILOpCode.Starg_s, 0x04, + (byte)ILOpCode.Starg_s, 0xFF, + 0xFE, 0x0B, 0x00, 0x01, 0x00, 0x00, + 0xFE, 0x0B, 0xFF, 0xFF, 0xFF, 0x7F + }, builder.ToArray()); + + Assert.Throws(() => il.StoreArgument(-1)); + Assert.Throws(() => il.StoreArgument(int.MinValue)); + } + + [Fact] + public void Branch() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder, new ControlFlowBuilder()); + var l = il.DefineLabel(); + + il.Branch(ILOpCode.Br_s, l); + il.Branch(ILOpCode.Brfalse_s, l); + il.Branch(ILOpCode.Brtrue_s, l); + il.Branch(ILOpCode.Beq_s, l); + il.Branch(ILOpCode.Bge_s, l); + il.Branch(ILOpCode.Bgt_s, l); + il.Branch(ILOpCode.Ble_s, l); + il.Branch(ILOpCode.Blt_s, l); + il.Branch(ILOpCode.Bne_un_s, l); + il.Branch(ILOpCode.Bge_un_s, l); + il.Branch(ILOpCode.Bgt_un_s, l); + il.Branch(ILOpCode.Ble_un_s, l); + il.Branch(ILOpCode.Blt_un_s, l); + il.Branch(ILOpCode.Leave_s, l); + il.Branch(ILOpCode.Br, l); + il.Branch(ILOpCode.Brfalse, l); + il.Branch(ILOpCode.Brtrue, l); + il.Branch(ILOpCode.Beq, l); + il.Branch(ILOpCode.Bge, l); + il.Branch(ILOpCode.Bgt, l); + il.Branch(ILOpCode.Ble, l); + il.Branch(ILOpCode.Blt, l); + il.Branch(ILOpCode.Bne_un, l); + il.Branch(ILOpCode.Bge_un, l); + il.Branch(ILOpCode.Bgt_un, l); + il.Branch(ILOpCode.Ble_un, l); + il.Branch(ILOpCode.Blt_un, l); + il.Branch(ILOpCode.Leave, l); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Br_s, 0xff, + (byte)ILOpCode.Brfalse_s, 0xff, + (byte)ILOpCode.Brtrue_s, 0xff, + (byte)ILOpCode.Beq_s, 0xff, + (byte)ILOpCode.Bge_s, 0xff, + (byte)ILOpCode.Bgt_s, 0xff, + (byte)ILOpCode.Ble_s, 0xff, + (byte)ILOpCode.Blt_s, 0xff, + (byte)ILOpCode.Bne_un_s, 0xff, + (byte)ILOpCode.Bge_un_s, 0xff, + (byte)ILOpCode.Bgt_un_s, 0xff, + (byte)ILOpCode.Ble_un_s, 0xff, + (byte)ILOpCode.Blt_un_s, 0xff, + (byte)ILOpCode.Leave_s, 0xff, + (byte)ILOpCode.Br, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Brfalse, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Brtrue, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Beq, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Bge, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Bgt, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Ble, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Blt, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Bne_un, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Bge_un, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Bgt_un, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Ble_un, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Blt_un, 0xff, 0xff, 0xff, 0xff, + (byte)ILOpCode.Leave, 0xff, 0xff, 0xff, 0xff, + }, builder.ToArray()); + } + + [Fact] + public void BranchAndLabel_Errors() + { + var builder = new BlobBuilder(); + var il = new InstructionEncoder(builder); + var ilcf1 = new InstructionEncoder(builder, new ControlFlowBuilder()); + var ilcf2 = new InstructionEncoder(builder, new ControlFlowBuilder()); + + var l1 = ilcf1.DefineLabel(); + ilcf2.DefineLabel(); + var l2 = ilcf2.DefineLabel(); + + Assert.Throws(() => il.DefineLabel()); + Assert.Throws(() => il.Branch(ILOpCode.Br, default(LabelHandle))); + Assert.Throws(() => il.MarkLabel(default(LabelHandle))); + + Assert.Throws(() => ilcf1.Branch(ILOpCode.Br, default(LabelHandle))); + Assert.Throws(() => ilcf1.MarkLabel(default(LabelHandle))); + Assert.Throws(() => ilcf1.Branch(ILOpCode.Br, l2)); + Assert.Throws(() => ilcf1.MarkLabel(l2)); + + Assert.Throws(() => ilcf1.Branch(ILOpCode.Box, l1)); + } + + [Fact] + public void MarkLabel() + { + var code = new BlobBuilder(); + var il = new InstructionEncoder(code, new ControlFlowBuilder()); + + var l = il.DefineLabel(); + + il.Branch(ILOpCode.Br_s, l); + + il.MarkLabel(l); + il.OpCode(ILOpCode.Nop); + il.OpCode(ILOpCode.Nop); + + il.MarkLabel(l); + il.OpCode(ILOpCode.Ret); + + var builder = new BlobBuilder(); + new MethodBodyStreamEncoder(builder).AddMethodBody(il); + + AssertEx.Equal(new byte[] + { + 0x16, // header + (byte)ILOpCode.Br_s, 0x02, + (byte)ILOpCode.Nop, + (byte)ILOpCode.Nop, + (byte)ILOpCode.Ret, + }, builder.ToArray()); + } + } +} diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyEncoderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyEncoderTests.cs deleted file mode 100644 index a45df5ac5872..000000000000 --- a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyEncoderTests.cs +++ /dev/null @@ -1,264 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using System.Reflection.Metadata.Tests; -using Xunit; - -namespace System.Reflection.Metadata.Ecma335.Tests -{ - public class MethodBodyEncoderTests - { - private static void WriteFakeILWithBranches(BlobBuilder builder, BranchBuilder branchBuilder, int size) - { - Assert.Equal(0, builder.Count); - - const byte filling = 0x01; - int ilOffset = 0; - foreach (var branch in branchBuilder.Branches) - { - builder.WriteBytes(filling, branch.ILOffset - ilOffset); - - Assert.Equal(branch.ILOffset, builder.Count); - builder.WriteByte(branch.ShortOpCode); - builder.WriteByte(0xff); - - ilOffset = branch.ILOffset + 2; - } - - builder.WriteBytes(filling, size - ilOffset); - Assert.Equal(size, builder.Count); - } - - [Fact] - public unsafe void TinyBody() - { - var bodyBuilder = new BlobBuilder(); - var codeBuilder = new BlobBuilder(); - var branchBuilder = new BranchBuilder(); - var il = new InstructionEncoder(codeBuilder, branchBuilder); - - var bodyEncoder = new MethodBodyEncoder( - bodyBuilder, - maxStack: 2, - exceptionRegionCount: 0, - localVariablesSignature: default(StandaloneSignatureHandle), - attributes: MethodBodyAttributes.None); - - Assert.True(bodyEncoder.IsTiny(10)); - Assert.True(bodyEncoder.IsTiny(63)); - Assert.False(bodyEncoder.IsTiny(64)); - - codeBuilder.WriteBytes(1, 61); - var l1 = il.DefineLabel(); - il.MarkLabel(l1); - - Assert.Equal(61, branchBuilder.Labels.Single()); - - il.Branch(ILOpCode.Br, l1); - - var brInfo = branchBuilder.Branches.Single(); - Assert.Equal(61, brInfo.ILOffset); - Assert.Equal(l1, brInfo.Label); - Assert.Equal((byte)ILOpCode.Br_s, brInfo.ShortOpCode); - - AssertEx.Equal(new byte[] { 1, (byte)ILOpCode.Br_s, unchecked((byte)-1) }, codeBuilder.ToArray(60, 3)); - - int bodyOffset; - bodyEncoder.WriteInstructions(codeBuilder, branchBuilder, out bodyOffset); - - var bodyBytes = bodyBuilder.ToArray(); - - AssertEx.Equal(new byte[] - { - 0xFE, // tiny header - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE - }, bodyBytes); - - fixed (byte* bodyPtr = &bodyBytes[0]) - { - var body = MethodBodyBlock.Create(new BlobReader(bodyPtr, bodyBytes.Length)); - - Assert.Equal(0, body.ExceptionRegions.Length); - Assert.Equal(default(StandaloneSignatureHandle), body.LocalSignature); - Assert.Equal(8, body.MaxStack); - Assert.Equal(bodyBytes.Length, body.Size); - - var ilBytes = body.GetILBytes(); - AssertEx.Equal(new byte[] - { - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE - }, ilBytes); - } - } - - [Fact] - public unsafe void FatBody() - { - var bodyBuilder = new BlobBuilder(); - var codeBuilder = new BlobBuilder(); - var branchBuilder = new BranchBuilder(); - var il = new InstructionEncoder(codeBuilder, branchBuilder); - - var bodyEncoder = new MethodBodyEncoder( - bodyBuilder, - maxStack: 2, - exceptionRegionCount: 0, - localVariablesSignature: default(StandaloneSignatureHandle), - attributes: MethodBodyAttributes.None); - - Assert.True(bodyEncoder.IsTiny(10)); - Assert.True(bodyEncoder.IsTiny(63)); - Assert.False(bodyEncoder.IsTiny(64)); - - codeBuilder.WriteBytes(1, 62); - var l1 = il.DefineLabel(); - il.MarkLabel(l1); - - Assert.Equal(62, branchBuilder.Labels.Single()); - - il.Branch(ILOpCode.Br, l1); - - var brInfo = branchBuilder.Branches.Single(); - Assert.Equal(62, brInfo.ILOffset); - Assert.Equal(l1, brInfo.Label); - Assert.Equal((byte)ILOpCode.Br_s, brInfo.ShortOpCode); - - AssertEx.Equal(new byte[] { 1, 1, (byte)ILOpCode.Br_s, unchecked((byte)-1) }, codeBuilder.ToArray(60, 4)); - - int bodyOffset; - bodyEncoder.WriteInstructions(codeBuilder, branchBuilder, out bodyOffset); - - var bodyBytes = bodyBuilder.ToArray(); - - AssertEx.Equal(new byte[] - { - 0x03, 0x30, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fat header - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE - }, bodyBytes); - - fixed (byte* bodyPtr = &bodyBytes[0]) - { - var body = MethodBodyBlock.Create(new BlobReader(bodyPtr, bodyBytes.Length)); - - Assert.Equal(0, body.ExceptionRegions.Length); - Assert.Equal(default(StandaloneSignatureHandle), body.LocalSignature); - Assert.Equal(2, body.MaxStack); - Assert.Equal(bodyBytes.Length, body.Size); - - var ilBytes = body.GetILBytes(); - AssertEx.Equal(new byte[] - { - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE - }, ilBytes); - } - } - - [Fact] - public void Branches1() - { - var branchBuilder = new BranchBuilder(); - - var l0 = branchBuilder.AddLabel(); - var l64 = branchBuilder.AddLabel(); - var l255 = branchBuilder.AddLabel(); - - branchBuilder.MarkLabel(0, l0); - branchBuilder.MarkLabel(64, l64); - branchBuilder.MarkLabel(255, l255); - - branchBuilder.AddBranch(0, l255, (byte)ILOpCode.Bge_s); - branchBuilder.AddBranch(16, l0, (byte)ILOpCode.Bge_un_s); // blob boundary - branchBuilder.AddBranch(33, l255, (byte)ILOpCode.Ble_s); // blob boundary - branchBuilder.AddBranch(35, l0, (byte)ILOpCode.Ble_un_s); // branches immediately next to each other - branchBuilder.AddBranch(37, l255, (byte)ILOpCode.Blt_s); // branches immediately next to each other - branchBuilder.AddBranch(40, l64, (byte)ILOpCode.Blt_un_s); - branchBuilder.AddBranch(254, l0, (byte)ILOpCode.Brfalse_s); // long branch at the end - - var dstBuilder = new BlobBuilder(); - var srcBuilder = new BlobBuilder(capacity: 17); - WriteFakeILWithBranches(srcBuilder, branchBuilder, size: 256); - - branchBuilder.FixupBranches(srcBuilder, dstBuilder); - - AssertEx.Equal(new byte[] - { - (byte)ILOpCode.Bge, 0xFA, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - (byte)ILOpCode.Bge_un_s, 0xEE, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - (byte)ILOpCode.Ble, 0xD9, 0x00, 0x00, 0x00, - (byte)ILOpCode.Ble_un_s, 0xDB, - (byte)ILOpCode.Blt, 0xD5, 0x00, 0x00, 0x00, - 0x01, - (byte)ILOpCode.Blt_un_s, 0x16, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01, - (byte)ILOpCode.Brfalse, 0xFD, 0xFE, 0xFF, 0xFF, - }, dstBuilder.ToArray()); - } - - [Fact] - public void BranchErrors() - { - var codeBuilder = new BlobBuilder(); - - var il = new InstructionEncoder(codeBuilder); - Assert.Throws(() => il.DefineLabel()); - - il = new InstructionEncoder(codeBuilder, new BranchBuilder()); - il.DefineLabel(); - il.DefineLabel(); - var l2 = il.DefineLabel(); - - var branchBuilder = new BranchBuilder(); - il = new InstructionEncoder(codeBuilder, branchBuilder); - var l0 = il.DefineLabel(); - - Assert.Throws(() => il.Branch(ILOpCode.Nop, l0)); - Assert.Throws(() => il.Branch(ILOpCode.Br, default(LabelHandle))); - Assert.Throws(() => il.Branch(ILOpCode.Br, l2)); - } - } -} diff --git a/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs new file mode 100644 index 000000000000..0de8f1137ea3 --- /dev/null +++ b/src/System.Reflection.Metadata/tests/Metadata/Ecma335/Encoding/MethodBodyStreamEncoderTests.cs @@ -0,0 +1,467 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Linq; +using System.Reflection.Metadata.Tests; +using Xunit; + +namespace System.Reflection.Metadata.Ecma335.Tests +{ + public class MethodBodyStreamEncoderTests + { + private static void WriteFakeILWithBranches(BlobBuilder builder, ControlFlowBuilder branchBuilder, int size) + { + Assert.Equal(0, builder.Count); + + const byte filling = 0x01; + int ilOffset = 0; + foreach (var branch in branchBuilder.Branches) + { + builder.WriteBytes(filling, branch.ILOffset - ilOffset); + + Assert.Equal(branch.ILOffset, builder.Count); + builder.WriteByte((byte)branch.OpCode); + + int operandSize = branch.OpCode.GetBranchOperandSize(); + if (operandSize == 1) + { + builder.WriteSByte(-1); + } + else + { + builder.WriteInt32(-1); + } + + ilOffset = branch.ILOffset + sizeof(byte) + operandSize; + } + + builder.WriteBytes(filling, size - ilOffset); + Assert.Equal(size, builder.Count); + } + + [Fact] + public void AddMethodBody_Errors() + { + var streamBuilder = new BlobBuilder(); + var il = new InstructionEncoder(new BlobBuilder()); + + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(default(InstructionEncoder))); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(il, -1)); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(il, ushort.MaxValue + 1)); + + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(codeSize: -1)); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(codeSize: 1, maxStack: -1)); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(codeSize: 1, maxStack: ushort.MaxValue + 1)); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(codeSize: 1, exceptionRegionCount: -1)); + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(codeSize: 1, exceptionRegionCount: 699051)); + } + + [Fact] + public void AddMethodBody_Reserved_Tiny1() + { + var streamBuilder = new BlobBuilder(32); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + streamBuilder.WriteBytes(0x01, 3); + + var body = encoder.AddMethodBody(10); + Assert.Equal(3, body.Offset); + + var segment = body.Instructions.GetBytes(); + Assert.Equal(4, segment.Offset); // +1 byte for the header + Assert.Equal(10, segment.Count); + + Assert.Null(body.ExceptionRegions.Builder); + + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, + 0x2A, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Tiny_AttributesIgnored() + { + var streamBuilder = new BlobBuilder(); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + var body = encoder.AddMethodBody(10, attributes: MethodBodyAttributes.None); + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x2A, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Tiny_MaxStackIgnored() + { + var streamBuilder = new BlobBuilder(); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + var body = encoder.AddMethodBody(10, maxStack: 7); + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x2A, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Tiny_Empty() + { + var streamBuilder = new BlobBuilder(); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + var body = encoder.AddMethodBody(0); + Assert.Equal(0, body.Offset); + + var segment = body.Instructions.GetBytes(); + Assert.Equal(1, segment.Offset); // +1 byte for the header + Assert.Equal(0, segment.Count); + + Assert.Null(body.ExceptionRegions.Builder); + + AssertEx.Equal(new byte[] + { + 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Fat1() + { + var streamBuilder = new BlobBuilder(32); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + streamBuilder.WriteBytes(0x01, 3); + + var body = encoder.AddMethodBody(10, maxStack: 9); + Assert.Equal(4, body.Offset); // 4B aligned + + var segment = body.Instructions.GetBytes(); + Assert.Equal(16, segment.Offset); // +12 byte for the header + Assert.Equal(10, segment.Count); + + Assert.Null(body.ExceptionRegions.Builder); + + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, + 0x00, // padding + 0x13, 0x30, + 0x09, 0x00, // max stack + 0x0A, 0x00, 0x00, 0x00, // code size + 0x00, 0x00, 0x00, 0x00, // local sig + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Fat2() + { + var streamBuilder = new BlobBuilder(32); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + streamBuilder.WriteBytes(0x01, 3); + + var body = encoder.AddMethodBody(10, localVariablesSignature: MetadataTokens.StandaloneSignatureHandle(0xABCDEF)); + Assert.Equal(4, body.Offset); // 4B aligned + + var segment = body.Instructions.GetBytes(); + Assert.Equal(16, segment.Offset); // +12 byte for the header + Assert.Equal(10, segment.Count); + + Assert.Null(body.ExceptionRegions.Builder); + + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, + 0x00, // padding + 0x13, 0x30, + 0x08, 0x00, // max stack + 0x0A, 0x00, 0x00, 0x00, // code size + 0xEF, 0xCD, 0xAB, 0x11, // local sig + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 + }, streamBuilder.ToArray()); + } + + [Fact] + public void AddMethodBody_Reserved_Exceptions_Fat1() + { + var streamBuilder = new BlobBuilder(32); + var encoder = new MethodBodyStreamEncoder(streamBuilder); + + streamBuilder.WriteBytes(0x01, 3); + + var body = encoder.AddMethodBody(10, exceptionRegionCount: 699050); + Assert.Equal(4, body.Offset); // 4B aligned + + var segment = body.Instructions.GetBytes(); + Assert.Equal(16, segment.Offset); // +12 byte for the header + Assert.Equal(10, segment.Count); + + Assert.Same(streamBuilder, body.ExceptionRegions.Builder); + + new BlobWriter(body.Instructions).WriteBytes(0x02, 10); + + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, + 0x00, // padding + 0x1B, 0x30, // flags + 0x08, 0x00, // max stack + 0x0A, 0x00, 0x00, 0x00, // code size + 0x00, 0x00, 0x00, 0x00, // local sig + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + + // exception table + + 0x00, 0x00, // padding + 0x41, // kind + 0xF4, 0xFF, 0xFF // size fo the table + }, streamBuilder.ToArray()); + } + + [Fact] + public unsafe void TinyBody() + { + var streamBuilder = new BlobBuilder(); + var codeBuilder = new BlobBuilder(); + var flowBuilder = new ControlFlowBuilder(); + + var il = new InstructionEncoder(codeBuilder, flowBuilder); + + codeBuilder.WriteBytes(1, 61); + var l1 = il.DefineLabel(); + il.MarkLabel(l1); + + Assert.Equal(61, flowBuilder.Labels.Single()); + + il.Branch(ILOpCode.Br_s, l1); + + var brInfo = flowBuilder.Branches.Single(); + Assert.Equal(61, brInfo.ILOffset); + Assert.Equal(l1, brInfo.Label); + Assert.Equal(ILOpCode.Br_s, brInfo.OpCode); + + AssertEx.Equal(new byte[] { 1, (byte)ILOpCode.Br_s, unchecked((byte)-1) }, codeBuilder.ToArray(60, 3)); + + var streamEncoder = new MethodBodyStreamEncoder(streamBuilder); + int bodyOffset = streamEncoder.AddMethodBody( + il, + maxStack: 2, + localVariablesSignature: default(StandaloneSignatureHandle), + attributes: MethodBodyAttributes.None); + + var bodyBytes = streamBuilder.ToArray(); + + AssertEx.Equal(new byte[] + { + 0xFE, // tiny header + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE + }, bodyBytes); + + fixed (byte* bodyPtr = &bodyBytes[0]) + { + var body = MethodBodyBlock.Create(new BlobReader(bodyPtr, bodyBytes.Length)); + + Assert.Equal(0, body.ExceptionRegions.Length); + Assert.Equal(default(StandaloneSignatureHandle), body.LocalSignature); + Assert.Equal(8, body.MaxStack); + Assert.Equal(bodyBytes.Length, body.Size); + + var ilBytes = body.GetILBytes(); + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE + }, ilBytes); + } + } + + [Fact] + public unsafe void FatBody() + { + var streamBuilder = new BlobBuilder(); + var codeBuilder = new BlobBuilder(); + var flowBuilder = new ControlFlowBuilder(); + var il = new InstructionEncoder(codeBuilder, flowBuilder); + + codeBuilder.WriteBytes(1, 62); + var l1 = il.DefineLabel(); + il.MarkLabel(l1); + + Assert.Equal(62, flowBuilder.Labels.Single()); + + il.Branch(ILOpCode.Br_s, l1); + + var brInfo = flowBuilder.Branches.Single(); + Assert.Equal(62, brInfo.ILOffset); + Assert.Equal(l1, brInfo.Label); + Assert.Equal(ILOpCode.Br_s, brInfo.OpCode); + + AssertEx.Equal(new byte[] { 1, 1, (byte)ILOpCode.Br_s, unchecked((byte)-1) }, codeBuilder.ToArray(60, 4)); + + var streamEncoder = new MethodBodyStreamEncoder(streamBuilder); + int bodyOffset = streamEncoder.AddMethodBody( + il, + maxStack: 2, + localVariablesSignature: default(StandaloneSignatureHandle), + attributes: MethodBodyAttributes.None); + + var bodyBytes = streamBuilder.ToArray(); + + AssertEx.Equal(new byte[] + { + 0x03, 0x30, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // fat header + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE + }, bodyBytes); + + fixed (byte* bodyPtr = &bodyBytes[0]) + { + var body = MethodBodyBlock.Create(new BlobReader(bodyPtr, bodyBytes.Length)); + + Assert.Equal(0, body.ExceptionRegions.Length); + Assert.Equal(default(StandaloneSignatureHandle), body.LocalSignature); + Assert.Equal(2, body.MaxStack); + Assert.Equal(bodyBytes.Length, body.Size); + + var ilBytes = body.GetILBytes(); + AssertEx.Equal(new byte[] + { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2B, 0xFE + }, ilBytes); + } + } + + [Fact] + public void Branches1() + { + var flowBuilder = new ControlFlowBuilder(); + + var l0 = flowBuilder.AddLabel(); + var l64 = flowBuilder.AddLabel(); + var l255 = flowBuilder.AddLabel(); + + flowBuilder.MarkLabel(0, l0); + flowBuilder.MarkLabel(64, l64); + flowBuilder.MarkLabel(255, l255); + + flowBuilder.AddBranch(0, l255, ILOpCode.Bge); + flowBuilder.AddBranch(16, l0, ILOpCode.Bge_un_s); // blob boundary + flowBuilder.AddBranch(33, l255, ILOpCode.Ble); // blob boundary + flowBuilder.AddBranch(38, l0, ILOpCode.Ble_un_s); // branches immediately next to each other + flowBuilder.AddBranch(40, l255, ILOpCode.Blt); // branches immediately next to each other + flowBuilder.AddBranch(46, l64, ILOpCode.Blt_un_s); + flowBuilder.AddBranch(254, l0, ILOpCode.Brfalse); // long branch at the end + + var dstBuilder = new BlobBuilder(); + var srcBuilder = new BlobBuilder(capacity: 17); + WriteFakeILWithBranches(srcBuilder, flowBuilder, size: 259); + + flowBuilder.CopyCodeAndFixupBranches(srcBuilder, dstBuilder); + + AssertEx.Equal(new byte[] + { + (byte)ILOpCode.Bge, 0xFA, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + (byte)ILOpCode.Bge_un_s, 0xEE, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + (byte)ILOpCode.Ble, 0xD9, 0x00, 0x00, 0x00, + (byte)ILOpCode.Ble_un_s, 0xD8, + (byte)ILOpCode.Blt, 0xD2, 0x00, 0x00, 0x00, + 0x01, + (byte)ILOpCode.Blt_un_s, 0x10, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + (byte)ILOpCode.Brfalse, 0xFD, 0xFE, 0xFF, 0xFF, + }, dstBuilder.ToArray()); + } + + [Fact] + public void BranchErrors() + { + var codeBuilder = new BlobBuilder(); + + var il = new InstructionEncoder(codeBuilder); + Assert.Throws(() => il.DefineLabel()); + + il = new InstructionEncoder(codeBuilder, new ControlFlowBuilder()); + il.DefineLabel(); + il.DefineLabel(); + var l2 = il.DefineLabel(); + + var flowBuilder = new ControlFlowBuilder(); + il = new InstructionEncoder(codeBuilder, flowBuilder); + var l0 = il.DefineLabel(); + + Assert.Throws(() => il.Branch(ILOpCode.Nop, l0)); + Assert.Throws(() => il.Branch(ILOpCode.Br, default(LabelHandle))); + Assert.Throws(() => il.Branch(ILOpCode.Br, l2)); + } + + [Fact] + public void BranchErrors_UnmarkedLabel() + { + var streamBuilder = new BlobBuilder(); + var codeBuilder = new BlobBuilder(); + var flowBuilder = new ControlFlowBuilder(); + + var il = new InstructionEncoder(codeBuilder, flowBuilder); + var l = il.DefineLabel(); + il.Branch(ILOpCode.Br_s, l); + il.OpCode(ILOpCode.Ret); + + Assert.Throws(() => new MethodBodyStreamEncoder(streamBuilder).AddMethodBody(il)); + } + } +} diff --git a/src/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs b/src/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs index f49d72d92f71..67c89673408a 100644 --- a/src/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs +++ b/src/System.Reflection.Metadata/tests/PortableExecutable/PEBuilderTests.cs @@ -205,16 +205,14 @@ private static MethodDefinitionHandle BasicValidationEmit(MetadataBuilder metada MethodSignature(). Parameters(0, returnType => returnType.Void(), parameters => { }); - var methodBodies = new MethodBodiesEncoder(ilBuilder); + var methodBodyStream = new MethodBodyStreamEncoder(ilBuilder); var codeBuilder = new BlobBuilder(); - var branchBuilder = new BranchBuilder(); InstructionEncoder il; // // Program::.ctor // - int ctorBodyOffset; il = new InstructionEncoder(codeBuilder); // ldarg.0 @@ -226,18 +224,23 @@ private static MethodDefinitionHandle BasicValidationEmit(MetadataBuilder metada // ret il.OpCode(ILOpCode.Ret); - methodBodies.AddMethodBody().WriteInstructions(codeBuilder, out ctorBodyOffset); + int ctorBodyOffset = methodBodyStream.AddMethodBody(il); codeBuilder.Clear(); // // Program::Main // - int mainBodyOffset; - il = new InstructionEncoder(codeBuilder, branchBuilder); - var endLabel = il.DefineLabel(); + var flowBuilder = new ControlFlowBuilder(); + il = new InstructionEncoder(codeBuilder, flowBuilder); + + var tryStart = il.DefineLabel(); + var tryEnd = il.DefineLabel(); + var finallyStart = il.DefineLabel(); + var finallyEnd = il.DefineLabel(); + flowBuilder.AddFinallyRegion(tryStart, tryEnd, finallyStart, finallyEnd); // .try - int tryOffset = il.Offset; + il.MarkLabel(tryStart); // ldstr "hello" il.LoadString(metadata.GetOrAddUserString("hello")); @@ -246,10 +249,11 @@ private static MethodDefinitionHandle BasicValidationEmit(MetadataBuilder metada il.Call(consoleWriteLineMemberRef); // leave.s END - il.Branch(ILOpCode.Leave, endLabel); - + il.Branch(ILOpCode.Leave_s, finallyEnd); + il.MarkLabel(tryEnd); + // .finally - int handlerOffset = il.Offset; + il.MarkLabel(finallyStart); // ldstr "world" il.LoadString(metadata.GetOrAddUserString("world")); @@ -259,22 +263,14 @@ private static MethodDefinitionHandle BasicValidationEmit(MetadataBuilder metada // .endfinally il.OpCode(ILOpCode.Endfinally); - int handlerEnd = il.Offset; - - // END: - il.MarkLabel(endLabel); + il.MarkLabel(finallyEnd); // ret il.OpCode(ILOpCode.Ret); - var body = methodBodies.AddMethodBody(exceptionRegionCount: 1); - var eh = body.WriteInstructions(codeBuilder, branchBuilder, out mainBodyOffset); - eh.StartRegions(); - eh.AddFinally(tryOffset, handlerOffset - tryOffset, handlerOffset, handlerEnd - handlerOffset); - eh.EndRegions(); - + int mainBodyOffset = methodBodyStream.AddMethodBody(il); codeBuilder.Clear(); - branchBuilder.Clear(); + flowBuilder.Clear(); var mainMethodDef = metadata.AddMethodDefinition( MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, @@ -434,24 +430,20 @@ private static MethodDefinitionHandle ComplexEmit(MetadataBuilder metadata, Blob metadata.GetOrAddString("_bc"), metadata.GetOrAddBlob(BuildSignature(e => e.FieldSignature().Type(type: baseClassTypeDef, isValueType: false)))); - var methodBodies = new MethodBodiesEncoder(ilBuilder); + var methodBodyStream = new MethodBodyStreamEncoder(ilBuilder); var buffer = new BlobBuilder(); - InstructionEncoder il; + var il = new InstructionEncoder(buffer); // // Foo // - int fooBodyOffset; - il = new InstructionEncoder(buffer); - il.LoadString(metadata.GetOrAddUserString("asdsad")); il.OpCode(ILOpCode.Newobj); il.Token(invalidOperationExceptionTypeRef); il.OpCode(ILOpCode.Throw); - methodBodies.AddMethodBody().WriteInstructions(buffer, out fooBodyOffset); - buffer.Clear(); + int fooBodyOffset = methodBodyStream.AddMethodBody(il); // Method1 var derivedClassFooMethodDef = metadata.AddMethodDefinition( diff --git a/src/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj b/src/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj index a2e98a3c48eb..24c2690a8283 100644 --- a/src/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj +++ b/src/System.Reflection.Metadata/tests/System.Reflection.Metadata.Tests.csproj @@ -37,6 +37,9 @@ + + + @@ -55,7 +58,7 @@ - +