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 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Collections.Immutable; 7 using System.Diagnostics; 8 using System.Reflection.Internal; 9 10 namespace System.Reflection.Metadata.Ecma335 11 { 12 public sealed class ControlFlowBuilder 13 { 14 // internal for testing: 15 internal readonly struct BranchInfo 16 { 17 internal readonly int ILOffset; 18 internal readonly LabelHandle Label; 19 private readonly byte _opCode; 20 21 internal ILOpCode OpCode => (ILOpCode)_opCode; 22 BranchInfoSystem.Reflection.Metadata.Ecma335.ControlFlowBuilder.BranchInfo23 internal BranchInfo(int ilOffset, LabelHandle label, ILOpCode opCode) 24 { 25 ILOffset = ilOffset; 26 Label = label; 27 _opCode = (byte)opCode; 28 } 29 IsShortBranchDistanceSystem.Reflection.Metadata.Ecma335.ControlFlowBuilder.BranchInfo30 internal bool IsShortBranchDistance(ImmutableArray<int>.Builder labels, out int distance) 31 { 32 const int shortBranchSize = 2; 33 const int longBranchSize = 5; 34 35 int labelTargetOffset = labels[Label.Id - 1]; 36 if (labelTargetOffset < 0) 37 { 38 Throw.InvalidOperation_LabelNotMarked(Label.Id); 39 } 40 41 distance = labelTargetOffset - (ILOffset + shortBranchSize); 42 if (unchecked((sbyte)distance) == distance) 43 { 44 return true; 45 } 46 47 distance = labelTargetOffset - (ILOffset + longBranchSize); 48 return false; 49 } 50 } 51 52 internal readonly struct ExceptionHandlerInfo 53 { 54 public readonly ExceptionRegionKind Kind; 55 public readonly LabelHandle TryStart, TryEnd, HandlerStart, HandlerEnd, FilterStart; 56 public readonly EntityHandle CatchType; 57 ExceptionHandlerInfoSystem.Reflection.Metadata.Ecma335.ControlFlowBuilder.ExceptionHandlerInfo58 public ExceptionHandlerInfo( 59 ExceptionRegionKind kind, 60 LabelHandle tryStart, 61 LabelHandle tryEnd, 62 LabelHandle handlerStart, 63 LabelHandle handlerEnd, 64 LabelHandle filterStart, 65 EntityHandle catchType) 66 { 67 Kind = kind; 68 TryStart = tryStart; 69 TryEnd = tryEnd; 70 HandlerStart = handlerStart; 71 HandlerEnd = handlerEnd; 72 FilterStart = filterStart; 73 CatchType = catchType; 74 } 75 } 76 77 private readonly ImmutableArray<BranchInfo>.Builder _branches; 78 private readonly ImmutableArray<int>.Builder _labels; 79 private ImmutableArray<ExceptionHandlerInfo>.Builder _lazyExceptionHandlers; 80 ControlFlowBuilder()81 public ControlFlowBuilder() 82 { 83 _branches = ImmutableArray.CreateBuilder<BranchInfo>(); 84 _labels = ImmutableArray.CreateBuilder<int>(); 85 } 86 Clear()87 internal void Clear() 88 { 89 _branches.Clear(); 90 _labels.Clear(); 91 _lazyExceptionHandlers?.Clear(); 92 } 93 AddLabel()94 internal LabelHandle AddLabel() 95 { 96 _labels.Add(-1); 97 return new LabelHandle(_labels.Count); 98 } 99 AddBranch(int ilOffset, LabelHandle label, ILOpCode opCode)100 internal void AddBranch(int ilOffset, LabelHandle label, ILOpCode opCode) 101 { 102 Debug.Assert(ilOffset >= 0); 103 Debug.Assert(_branches.Count == 0 || ilOffset > _branches.Last().ILOffset); 104 ValidateLabel(label, nameof(label)); 105 _branches.Add(new BranchInfo(ilOffset, label, opCode)); 106 } 107 MarkLabel(int ilOffset, LabelHandle label)108 internal void MarkLabel(int ilOffset, LabelHandle label) 109 { 110 Debug.Assert(ilOffset >= 0); 111 ValidateLabel(label, nameof(label)); 112 _labels[label.Id - 1] = ilOffset; 113 } 114 GetLabelOffsetChecked(LabelHandle label)115 private int GetLabelOffsetChecked(LabelHandle label) 116 { 117 int offset = _labels[label.Id - 1]; 118 if (offset < 0) 119 { 120 Throw.InvalidOperation_LabelNotMarked(label.Id); 121 } 122 123 return offset; 124 } 125 ValidateLabel(LabelHandle label, string parameterName)126 private void ValidateLabel(LabelHandle label, string parameterName) 127 { 128 if (label.IsNil) 129 { 130 Throw.ArgumentNull(parameterName); 131 } 132 133 if (label.Id > _labels.Count) 134 { 135 Throw.LabelDoesntBelongToBuilder(parameterName); 136 } 137 } 138 139 /// <summary> 140 /// Adds finally region. 141 /// </summary> 142 /// <param name="tryStart">Label marking the first instruction of the try block.</param> 143 /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param> 144 /// <param name="handlerStart">Label marking the first instruction of the handler.</param> 145 /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param> 146 /// <returns>Encoder for the next clause.</returns> 147 /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associated with.</exception> 148 /// <exception cref="ArgumentNullException">A label has default value.</exception> AddFinallyRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd)149 public void AddFinallyRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) => 150 AddExceptionRegion(ExceptionRegionKind.Finally, tryStart, tryEnd, handlerStart, handlerEnd); 151 152 /// <summary> 153 /// Adds fault region. 154 /// </summary> 155 /// <param name="tryStart">Label marking the first instruction of the try block.</param> 156 /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param> 157 /// <param name="handlerStart">Label marking the first instruction of the handler.</param> 158 /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param> 159 /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associated with.</exception> 160 /// <exception cref="ArgumentNullException">A label has default value.</exception> AddFaultRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd)161 public void AddFaultRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd) => 162 AddExceptionRegion(ExceptionRegionKind.Fault, tryStart, tryEnd, handlerStart, handlerEnd); 163 164 /// <summary> 165 /// Adds catch region. 166 /// </summary> 167 /// <param name="tryStart">Label marking the first instruction of the try block.</param> 168 /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param> 169 /// <param name="handlerStart">Label marking the first instruction of the handler.</param> 170 /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param> 171 /// <param name="catchType">The type of exception to be caught: <see cref="TypeDefinitionHandle"/>, <see cref="TypeReferenceHandle"/> or <see cref="TypeSpecificationHandle"/>.</param> 172 /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associated with.</exception> 173 /// <exception cref="ArgumentException"><paramref name="catchType"/> is not a valid type handle.</exception> 174 /// <exception cref="ArgumentNullException">A label has default value.</exception> AddCatchRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, EntityHandle catchType)175 public void AddCatchRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, EntityHandle catchType) 176 { 177 if (!ExceptionRegionEncoder.IsValidCatchTypeHandle(catchType)) 178 { 179 Throw.InvalidArgument_Handle(nameof(catchType)); 180 } 181 182 AddExceptionRegion(ExceptionRegionKind.Catch, tryStart, tryEnd, handlerStart, handlerEnd, catchType: catchType); 183 } 184 185 /// <summary> 186 /// Adds catch region. 187 /// </summary> 188 /// <param name="tryStart">Label marking the first instruction of the try block.</param> 189 /// <param name="tryEnd">Label marking the instruction immediately following the try block.</param> 190 /// <param name="handlerStart">Label marking the first instruction of the handler.</param> 191 /// <param name="handlerEnd">Label marking the instruction immediately following the handler.</param> 192 /// <param name="filterStart">Label marking the first instruction of the filter block.</param> 193 /// <exception cref="ArgumentException">A label was not defined by an instruction encoder this builder is associated with.</exception> 194 /// <exception cref="ArgumentNullException">A label has default value.</exception> AddFilterRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, LabelHandle filterStart)195 public void AddFilterRegion(LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, LabelHandle filterStart) 196 { 197 ValidateLabel(filterStart, nameof(filterStart)); 198 AddExceptionRegion(ExceptionRegionKind.Filter, tryStart, tryEnd, handlerStart, handlerEnd, filterStart: filterStart); 199 } 200 AddExceptionRegion( ExceptionRegionKind kind, LabelHandle tryStart, LabelHandle tryEnd, LabelHandle handlerStart, LabelHandle handlerEnd, LabelHandle filterStart = default(LabelHandle), EntityHandle catchType = default(EntityHandle))201 private void AddExceptionRegion( 202 ExceptionRegionKind kind, 203 LabelHandle tryStart, 204 LabelHandle tryEnd, 205 LabelHandle handlerStart, 206 LabelHandle handlerEnd, 207 LabelHandle filterStart = default(LabelHandle), 208 EntityHandle catchType = default(EntityHandle)) 209 { 210 ValidateLabel(tryStart, nameof(tryStart)); 211 ValidateLabel(tryEnd, nameof(tryEnd)); 212 ValidateLabel(handlerStart, nameof(handlerStart)); 213 ValidateLabel(handlerEnd, nameof(handlerEnd)); 214 215 if (_lazyExceptionHandlers == null) 216 { 217 _lazyExceptionHandlers = ImmutableArray.CreateBuilder<ExceptionHandlerInfo>(); 218 } 219 220 _lazyExceptionHandlers.Add(new ExceptionHandlerInfo(kind, tryStart, tryEnd, handlerStart, handlerEnd, filterStart, catchType)); 221 } 222 223 // internal for testing: 224 internal IEnumerable<BranchInfo> Branches => _branches; 225 226 // internal for testing: 227 internal IEnumerable<int> Labels => _labels; 228 229 internal int BranchCount => _branches.Count; 230 231 internal int ExceptionHandlerCount => _lazyExceptionHandlers?.Count ?? 0; 232 233 /// <exception cref="InvalidOperationException" /> CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder)234 internal void CopyCodeAndFixupBranches(BlobBuilder srcBuilder, BlobBuilder dstBuilder) 235 { 236 var branch = _branches[0]; 237 int branchIndex = 0; 238 239 // offset within the source builder 240 int srcOffset = 0; 241 242 // current offset within the current source blob 243 int srcBlobOffset = 0; 244 245 foreach (Blob srcBlob in srcBuilder.GetBlobs()) 246 { 247 Debug.Assert( 248 srcBlobOffset == 0 || 249 srcBlobOffset == 1 && srcBlob.Buffer[0] == 0xff || 250 srcBlobOffset == 4 && srcBlob.Buffer[0] == 0xff && srcBlob.Buffer[1] == 0xff && srcBlob.Buffer[2] == 0xff && srcBlob.Buffer[3] == 0xff); 251 252 while (true) 253 { 254 // copy bytes preceding the next branch, or till the end of the blob: 255 int chunkSize = Math.Min(branch.ILOffset - srcOffset, srcBlob.Length - srcBlobOffset); 256 dstBuilder.WriteBytes(srcBlob.Buffer, srcBlobOffset, chunkSize); 257 srcOffset += chunkSize; 258 srcBlobOffset += chunkSize; 259 260 // there is no branch left in the blob: 261 if (srcBlobOffset == srcBlob.Length) 262 { 263 srcBlobOffset = 0; 264 break; 265 } 266 267 Debug.Assert(srcBlob.Buffer[srcBlobOffset] == (byte)branch.OpCode); 268 269 int operandSize = branch.OpCode.GetBranchOperandSize(); 270 bool isShortInstruction = operandSize == 1; 271 272 // Note: the 4B operand is contiguous since we wrote it via BlobBuilder.WriteInt32() 273 Debug.Assert( 274 srcBlobOffset + 1 == srcBlob.Length || 275 (isShortInstruction ? 276 srcBlob.Buffer[srcBlobOffset + 1] == 0xff : 277 BitConverter.ToUInt32(srcBlob.Buffer, srcBlobOffset + 1) == 0xffffffff)); 278 279 // write branch opcode: 280 dstBuilder.WriteByte(srcBlob.Buffer[srcBlobOffset]); 281 282 // write branch operand: 283 int branchDistance; 284 bool isShortDistance = branch.IsShortBranchDistance(_labels, out branchDistance); 285 286 if (isShortInstruction && !isShortDistance) 287 { 288 // We could potentially implement algortihm that automatically fixes up the branch instructions as well to accomodate bigger distances, 289 // however an optimal algorithm would be rather complex (something like: calculate topological ordering of crossing branch instructions 290 // 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 291 // distance is unknown upfront. If they do they probably already implement more sophisticad algorithm for IL layout optimization already. 292 throw new InvalidOperationException(SR.Format(SR.DistanceBetweenInstructionAndLabelTooBig, branch.OpCode, srcOffset, branchDistance)); 293 } 294 295 if (isShortInstruction) 296 { 297 dstBuilder.WriteSByte((sbyte)branchDistance); 298 } 299 else 300 { 301 dstBuilder.WriteInt32(branchDistance); 302 } 303 304 srcOffset += sizeof(byte) + operandSize; 305 306 // next branch: 307 branchIndex++; 308 if (branchIndex == _branches.Count) 309 { 310 branch = new BranchInfo(int.MaxValue, default(LabelHandle), 0); 311 } 312 else 313 { 314 branch = _branches[branchIndex]; 315 } 316 317 // the branch starts at the very end and its operand is in the next blob: 318 if (srcBlobOffset == srcBlob.Length - 1) 319 { 320 srcBlobOffset = operandSize; 321 break; 322 } 323 324 // skip fake branch instruction: 325 srcBlobOffset += sizeof(byte) + operandSize; 326 } 327 } 328 } 329 SerializeExceptionTable(BlobBuilder builder)330 internal void SerializeExceptionTable(BlobBuilder builder) 331 { 332 if (_lazyExceptionHandlers == null || _lazyExceptionHandlers.Count == 0) 333 { 334 return; 335 } 336 337 var regionEncoder = ExceptionRegionEncoder.SerializeTableHeader(builder, _lazyExceptionHandlers.Count, HasSmallExceptionRegions()); 338 339 foreach (var handler in _lazyExceptionHandlers) 340 { 341 // Note that labels have been validated when added to the handler list, 342 // they might not have been marked though. 343 344 int tryStart = GetLabelOffsetChecked(handler.TryStart); 345 int tryEnd = GetLabelOffsetChecked(handler.TryEnd); 346 int handlerStart = GetLabelOffsetChecked(handler.HandlerStart); 347 int handlerEnd = GetLabelOffsetChecked(handler.HandlerEnd); 348 349 if (tryStart > tryEnd) 350 { 351 Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, tryStart, tryEnd)); 352 } 353 354 if (handlerStart > handlerEnd) 355 { 356 Throw.InvalidOperation(SR.Format(SR.InvalidExceptionRegionBounds, handlerStart, handlerEnd)); 357 } 358 359 int catchTokenOrOffset; 360 switch (handler.Kind) 361 { 362 case ExceptionRegionKind.Catch: 363 catchTokenOrOffset = MetadataTokens.GetToken(handler.CatchType); 364 break; 365 366 case ExceptionRegionKind.Filter: 367 catchTokenOrOffset = GetLabelOffsetChecked(handler.FilterStart); 368 break; 369 370 default: 371 catchTokenOrOffset = 0; 372 break; 373 } 374 375 regionEncoder.AddUnchecked( 376 handler.Kind, 377 tryStart, 378 tryEnd - tryStart, 379 handlerStart, 380 handlerEnd - handlerStart, 381 catchTokenOrOffset); 382 } 383 } 384 HasSmallExceptionRegions()385 private bool HasSmallExceptionRegions() 386 { 387 Debug.Assert(_lazyExceptionHandlers != null); 388 389 if (!ExceptionRegionEncoder.IsSmallRegionCount(_lazyExceptionHandlers.Count)) 390 { 391 return false; 392 } 393 394 foreach (var handler in _lazyExceptionHandlers) 395 { 396 if (!ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.TryStart), GetLabelOffsetChecked(handler.TryEnd)) || 397 !ExceptionRegionEncoder.IsSmallExceptionRegionFromBounds(GetLabelOffsetChecked(handler.HandlerStart), GetLabelOffsetChecked(handler.HandlerEnd))) 398 { 399 return false; 400 } 401 } 402 403 return true; 404 } 405 } 406 } 407