< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 209
Coverable lines: 209
Total lines: 424
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 70
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%110%
GetBranchDistance(...)0%660%
.ctor(...)100%110%
.ctor()100%110%
Clear()0%220%
AddLabel()100%110%
AddBranch(...)0%12120%
MarkLabel(...)100%110%
GetLabelOffsetChecked(...)0%220%
ValidateLabel(...)0%440%
AddFinallyRegion(...)100%110%
AddFaultRegion(...)100%110%
AddCatchRegion(...)0%220%
AddFilterRegion(...)100%110%
AddExceptionRegion(...)0%220%
ValidateNotInSwitch()0%220%
SwitchBranchAdded()0%220%
CopyCodeAndFixupBranches(...)0%12120%
SerializeExceptionTable(...)0%14140%
HasSmallExceptionRegions()0%880%

File(s)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/Ecma335/Encoding/ControlFlowBuilder.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System.Collections.Generic;
 5using System.Diagnostics;
 6
 7namespace System.Reflection.Metadata.Ecma335
 8{
 9    public sealed class ControlFlowBuilder
 10    {
 11        // internal for testing:
 12        internal readonly struct BranchInfo
 13        {
 14            // The offset to the label operand inside the instruction.
 15            internal readonly int OperandOffset;
 16            internal readonly LabelHandle Label;
 17            // Label offsets are calculated from the end of the instruction that contains them.
 18            // This value contains the displacement from the start of the label operand
 19            // to the end of the instruction. It is equal to one on short branches,
 20            // four on long branches and bigger on the switch instruction.
 21            private readonly int _instructionEndDisplacement;
 22
 23            // The following two fields are used for error reporting and tests.
 24
 25            // The offset to the start of the instruction.
 26            internal readonly int ILOffset;
 27            internal readonly ILOpCode OpCode;
 28
 029            internal bool IsShortBranch => _instructionEndDisplacement == 1;
 030            internal int OperandSize => Math.Min(_instructionEndDisplacement, 4);
 31
 32            internal BranchInfo(int operandOffset, LabelHandle label, int instructionEndDisplacement, int ilOffset, ILOp
 033            {
 034                OperandOffset = operandOffset;
 035                Label = label;
 036                _instructionEndDisplacement = instructionEndDisplacement;
 037                ILOffset = ilOffset;
 038                OpCode = opCode;
 039            }
 40
 41            internal int GetBranchDistance(List<int> labels)
 042            {
 043                int labelTargetOffset = labels[Label.Id - 1];
 044                if (labelTargetOffset < 0)
 045                {
 046                    Throw.InvalidOperation_LabelNotMarked(Label.Id);
 47                }
 48
 049                int distance = labelTargetOffset - (OperandOffset + _instructionEndDisplacement);
 50
 051                if (IsShortBranch && unchecked((sbyte)distance) != distance)
 052                {
 53                    // We could potentially implement algorithm that automatically fixes up branch instructions to accom
 54                    // however an optimal algorithm would be rather complex (something like: calculate topological order
 55                    // and then use fixed point to eliminate cycles). If the caller doesn't care about optimal IL size t
 56                    // distance is unknown upfront. If they do they probably implement more sophisticated algorithm for 
 057                    throw new InvalidOperationException(SR.Format(SR.DistanceBetweenInstructionAndLabelTooBig, OpCode, I
 58                }
 59
 060                return distance;
 061            }
 62        }
 63
 64        internal readonly struct ExceptionHandlerInfo
 65        {
 66            public readonly ExceptionRegionKind Kind;
 67            public readonly LabelHandle TryStart, TryEnd, HandlerStart, HandlerEnd, FilterStart;
 68            public readonly EntityHandle CatchType;
 69
 70            public ExceptionHandlerInfo(
 71                ExceptionRegionKind kind,
 72                LabelHandle tryStart,
 73                LabelHandle tryEnd,
 74                LabelHandle handlerStart,
 75                LabelHandle handlerEnd,
 76                LabelHandle filterStart,
 77                EntityHandle catchType)
 078            {
 079                Kind = kind;
 080                TryStart = tryStart;
 081                TryEnd = tryEnd;
 082                HandlerStart = handlerStart;
 083                HandlerEnd = handlerEnd;
 084                FilterStart = filterStart;
 085                CatchType = catchType;
 086            }
 87        }
 88
 89        private readonly List<BranchInfo> _branches;
 90        private readonly List<int> _labels;
 91        private List<ExceptionHandlerInfo>? _lazyExceptionHandlers;
 92
 093        public ControlFlowBuilder()
 094        {
 095            _branches = new List<BranchInfo>();
 096            _labels = new List<int>();
 097        }
 98
 99        /// <summary>
 100        /// Clears the object's internal state, allowing the same instance to be reused.
 101        /// </summary>
 102        public void Clear()
 0103        {
 0104            _branches.Clear();
 0105            _labels.Clear();
 0106            _lazyExceptionHandlers?.Clear();
 0107            RemainingSwitchBranches = 0;
 0108        }
 109
 110        internal LabelHandle AddLabel()
 0111        {
 0112            ValidateNotInSwitch();
 0113            _labels.Add(-1);
 0114            return new LabelHandle(_labels.Count);
 0115        }
 116
 117        internal void AddBranch(int operandOffset, LabelHandle label, int instructionEndDisplacement, int ilOffset, ILOp
 0118        {
 0119            Debug.Assert(operandOffset >= 0);
 0120            Debug.Assert(_branches.Count == 0 || operandOffset > _branches[_branches.Count - 1].OperandOffset);
 0121            ValidateLabel(label, nameof(label));
 122#if DEBUG
 0123            switch (instructionEndDisplacement)
 124            {
 125                case 1:
 0126                    Debug.Assert(opCode.GetBranchOperandSize() == 1);
 0127                    break;
 128                case 4:
 0129                    Debug.Assert(opCode == ILOpCode.Switch || opCode.GetBranchOperandSize() == 4);
 0130                    break;
 131                default:
 0132                    Debug.Assert(instructionEndDisplacement > 4 && instructionEndDisplacement % 4 == 0 && opCode == ILOp
 0133                    break;
 134            }
 135#endif
 0136            _branches.Add(new BranchInfo(operandOffset, label, instructionEndDisplacement, ilOffset, opCode));
 0137        }
 138
 139        internal void MarkLabel(int ilOffset, LabelHandle label)
 0140        {
 0141            Debug.Assert(ilOffset >= 0);
 0142            ValidateNotInSwitch();
 0143            ValidateLabel(label, nameof(label));
 0144            _labels[label.Id - 1] = ilOffset;
 0145        }
 146
 147        private int GetLabelOffsetChecked(LabelHandle label)
 0148        {
 0149            int offset = _labels[label.Id - 1];
 0150            if (offset < 0)
 0151            {
 0152                Throw.InvalidOperation_LabelNotMarked(label.Id);
 153            }
 154
 0155            return offset;
 0156        }
 157
 158        private void ValidateLabel(LabelHandle label, string parameterName)
 0159        {
 0160            if (label.IsNil)
 0161            {
 0162                Throw.ArgumentNull(parameterName);
 163            }
 164
 0165            if (label.Id > _labels.Count)
 0166            {
 0167                Throw.LabelDoesntBelongToBuilder(parameterName);
 168            }
 0169        }
 170
 171        /// <summary>
 172        /// Adds finally region.
 173        /// </summary>
 174        /// <param name="tryStart">Label marking the first instruction of the try block.</param>
 175        /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param>
 176        /// <param name="handlerStart">Label marking the first instruction of the handler.</param>
 177        /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param>
 178        /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associ
 179        /// <exception cref="ArgumentNullException">A label has default value.</exception>
 180        public void AddFinallyRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle han
 0181            AddExceptionRegion(ExceptionRegionKind.Finally, tryStart, tryEnd, handlerStart, handlerEnd);
 182
 183        /// <summary>
 184        /// Adds fault region.
 185        /// </summary>
 186        /// <param name="tryStart">Label marking the first instruction of the try block.</param>
 187        /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param>
 188        /// <param name="handlerStart">Label marking the first instruction of the handler.</param>
 189        /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param>
 190        /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associ
 191        /// <exception cref="ArgumentNullException">A label has default value.</exception>
 192        public void AddFaultRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handl
 0193            AddExceptionRegion(ExceptionRegionKind.Fault, tryStart, tryEnd, handlerStart, handlerEnd);
 194
 195        /// <summary>
 196        /// Adds catch region.
 197        /// </summary>
 198        /// <param name="tryStart">Label marking the first instruction of the try block.</param>
 199        /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param>
 200        /// <param name="handlerStart">Label marking the first instruction of the handler.</param>
 201        /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param>
 202        /// <param name="catchType">The type of exception to be caught: <see cref="TypeDefinitionHandle"/>, <see cref="T
 203        /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associ
 204        /// <exception cref="ArgumentException"><paramref name="catchType"/> is not a valid type handle.</exception>
 205        /// <exception cref="ArgumentNullException">A label has default value.</exception>
 206        public void AddCatchRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handl
 0207        {
 0208            if (!ExceptionRegionEncoder.IsValidCatchTypeHandle(catchType))
 0209            {
 0210                Throw.InvalidArgument_Handle(nameof(catchType));
 211            }
 212
 0213            AddExceptionRegion(ExceptionRegionKind.Catch, tryStart, tryEnd, handlerStart, handlerEnd, catchType: catchTy
 0214        }
 215
 216        /// <summary>
 217        /// Adds catch region.
 218        /// </summary>
 219        /// <param name="tryStart">Label marking the first instruction of the try block.</param>
 220        /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param>
 221        /// <param name="handlerStart">Label marking the first instruction of the handler.</param>
 222        /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param>
 223        /// <param name="filterStart">Label marking the first instruction of the filter block.</param>
 224        /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associ
 225        /// <exception cref="ArgumentNullException">A label has default value.</exception>
 226        public void AddFilterRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle hand
 0227        {
 0228            ValidateLabel(filterStart, nameof(filterStart));
 0229            AddExceptionRegion(ExceptionRegionKind.Filter, tryStart, tryEnd, handlerStart, handlerEnd, filterStart: filt
 0230        }
 231
 232        private void AddExceptionRegion(
 233            ExceptionRegionKind kind,
 234            LabelHandle tryStart,
 235            LabelHandle tryEnd,
 236            LabelHandle handlerStart,
 237            LabelHandle handlerEnd,
 238            LabelHandle filterStart = default(LabelHandle),
 239            EntityHandle catchType = default(EntityHandle))
 0240        {
 0241            ValidateLabel(tryStart, nameof(tryStart));
 0242            ValidateLabel(tryEnd, nameof(tryEnd));
 0243            ValidateLabel(handlerStart, nameof(handlerStart));
 0244            ValidateLabel(handlerEnd, nameof(handlerEnd));
 0245            ValidateNotInSwitch();
 246
 0247            _lazyExceptionHandlers ??= new List<ExceptionHandlerInfo>();
 248
 0249            _lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filter
 0250        }
 251
 252        // internal for testing:
 0253        internal IEnumerable<BranchInfo> Branches => _branches;
 254
 255        // internal for testing:
 0256        internal IEnumerable<int> Labels => _labels;
 257
 0258        internal int BranchCount => _branches.Count;
 259
 0260        internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0;
 261
 0262        internal int RemainingSwitchBranches { get; set; }
 263
 264        internal void ValidateNotInSwitch()
 0265        {
 0266            if (RemainingSwitchBranches > 0)
 0267            {
 0268                Throw.InvalidOperation(SR.SwitchInstructionEncoderTooFewBranches);
 269            }
 0270        }
 271
 272        internal void SwitchBranchAdded()
 0273        {
 0274            if (RemainingSwitchBranches == 0)
 0275            {
 0276                Throw.InvalidOperation(SR.SwitchInstructionEncoderTooManyBranches);
 277            }
 0278            RemainingSwitchBranches--;
 0279        }
 280
 281        /// <exception cref="InvalidOperationException" />
 282        internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder)
 0283        {
 0284            var branch = _branches[0];
 0285            int branchIndex = 0;
 286
 287            // offset within the source builder
 0288            int srcOffset = 0;
 289
 290            // current offset within the current source blob
 0291            int srcBlobOffset = 0;
 292
 0293            foreach (Blob srcBlob in srcBuilder.GetBlobs())
 0294            {
 0295                Debug.Assert(srcBlobOffset == 0);
 296
 0297                while (true)
 0298                {
 299                    // copy bytes preceding the next branch, or till the end of the blob:
 0300                    int chunkSize = Math.Min(branch.OperandOffset - srcOffset, srcBlob.Length - srcBlobOffset);
 0301                    dstBuilder.WriteBytes(srcBlob.Buffer, srcBlobOffset, chunkSize);
 0302                    srcOffset += chunkSize;
 0303                    srcBlobOffset += chunkSize;
 304
 305                    // there is no branch left in the blob:
 0306                    if (srcBlobOffset == srcBlob.Length)
 0307                    {
 0308                        srcBlobOffset = 0;
 0309                        break;
 310                    }
 311
 0312                    int operandSize = branch.OperandSize;
 0313                    bool isShortInstruction = branch.IsShortBranch;
 314
 315                    // Note: the 4B operand is contiguous since we wrote it via BlobBuilder.WriteInt32()
 0316                    Debug.Assert(
 0317                        srcBlobOffset == srcBlob.Length ||
 0318                        (isShortInstruction ?
 0319                           srcBlob.Buffer[srcBlobOffset] == 0xff :
 0320                           BitConverter.ToUInt32(srcBlob.Buffer, srcBlobOffset) == 0xffffffff));
 321
 0322                    int branchDistance = branch.GetBranchDistance(_labels);
 323
 324                    // write branch operand:
 0325                    if (isShortInstruction)
 0326                    {
 0327                        dstBuilder.WriteSByte((sbyte)branchDistance);
 0328                    }
 329                    else
 0330                    {
 0331                        dstBuilder.WriteInt32(branchDistance);
 0332                    }
 333
 0334                    srcOffset += operandSize;
 335
 336                    // next branch:
 0337                    branchIndex++;
 0338                    if (branchIndex == _branches.Count)
 0339                    {
 340                        // We have processed all branches. The MaxValue will cause the rest
 341                        // of the IL stream to be directly copied to the destination blob.
 0342                        branch = new BranchInfo(operandOffset: int.MaxValue, label: default,
 0343                            instructionEndDisplacement: default, ilOffset: default, opCode: default);
 0344                    }
 345                    else
 0346                    {
 0347                        branch = _branches[branchIndex];
 0348                    }
 349
 350                    // skip fake branch operand:
 0351                    srcBlobOffset += operandSize;
 0352                }
 0353            }
 0354        }
 355
 356        internal void SerializeExceptionTable(BlobBuilder builder)
 0357        {
 0358            if (_lazyExceptionHandlers == null || _lazyExceptionHandlers.Count == 0)
 0359            {
 0360                return;
 361            }
 362
 0363            var regionEncoder = ExceptionRegionEncoder.SerializeTableHeader(builder, _lazyExceptionHandlers.Count, HasSm
 364
 0365            foreach (var handler in _lazyExceptionHandlers)
 0366            {
 367                // Note that labels have been validated when added to the handler list,
 368                // they might not have been marked though.
 369
 0370                int tryStart = GetLabelOffsetChecked(handler.TryStart);
 0371                int tryEnd = GetLabelOffsetChecked(handler.TryEnd);
 0372                int handlerStart = GetLabelOffsetChecked(handler.HandlerStart);
 0373                int handlerEnd = GetLabelOffsetChecked(handler.HandlerEnd);
 374
 0375                if (tryStart > tryEnd)
 0376                {
 0377                    Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, tryStart, tryEnd));
 378                }
 379
 0380                if (handlerStart > handlerEnd)
 0381                {
 0382                    Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, handlerStart, handlerEnd));
 383                }
 384
 0385                int catchTokenOrOffset = handler.Kind switch
 0386                {
 0387                    ExceptionRegionKind.Catch => MetadataTokens.GetToken(handler.CatchType),
 0388                    ExceptionRegionKind.Filter => GetLabelOffsetChecked(handler.FilterStart),
 0389                    _ => 0,
 0390                };
 391
 0392                regionEncoder.AddUnchecked(
 0393                    handler.Kind,
 0394                    tryStart,
 0395                    tryEnd - tryStart,
 0396                    handlerStart,
 0397                    handlerEnd - handlerStart,
 0398                    catchTokenOrOffset);
 0399            }
 0400        }
 401
 402        private bool HasSmallExceptionRegions()
 0403        {
 0404            Debug.Assert(_lazyExceptionHandlers != null);
 405
 0406            if (!ExceptionRegionEncoder.IsSmallRegionCount(_lazyExceptionHandlers.Count))
 0407            {
 0408                return false;
 409            }
 410
 0411            foreach (var handler in _lazyExceptionHandlers)
 0412            {
 0413                if (!ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.TryStart), Ge
 0414                    !ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.HandlerStart)
 0415                {
 0416                    return false;
 417                }
 0418            }
 419
 0420            return true;
 0421        }
 422    }
 423}
 424

Methods/Properties

IsShortBranch()
OperandSize()
.ctor(System.Int32,System.Reflection.Metadata.Ecma335.LabelHandle,System.Int32,System.Int32,System.Reflection.Metadata.ILOpCode)
GetBranchDistance(System.Collections.Generic.List`1<System.Int32>)
.ctor(System.Reflection.Metadata.ExceptionRegionKind,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.EntityHandle)
.ctor()
Clear()
AddLabel()
AddBranch(System.Int32,System.Reflection.Metadata.Ecma335.LabelHandle,System.Int32,System.Int32,System.Reflection.Metadata.ILOpCode)
MarkLabel(System.Int32,System.Reflection.Metadata.Ecma335.LabelHandle)
GetLabelOffsetChecked(System.Reflection.Metadata.Ecma335.LabelHandle)
ValidateLabel(System.Reflection.Metadata.Ecma335.LabelHandle,System.String)
AddFinallyRegion(System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle)
AddFaultRegion(System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle)
AddCatchRegion(System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.EntityHandle)
AddFilterRegion(System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle)
AddExceptionRegion(System.Reflection.Metadata.ExceptionRegionKind,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.Ecma335.LabelHandle,System.Reflection.Metadata.EntityHandle)
Branches()
Labels()
BranchCount()
ExceptionHandlerCount()
RemainingSwitchBranches()
ValidateNotInSwitch()
SwitchBranchAdded()
CopyCodeAndFixupBranches(System.Reflection.Metadata.BlobBuilder,System.Reflection.Metadata.BlobBuilder)
SerializeExceptionTable(System.Reflection.Metadata.BlobBuilder)
HasSmallExceptionRegions()