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 @@
-
+