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.Diagnostics; 7 using System.Diagnostics.CodeAnalysis; 8 using System.Dynamic.Utils; 9 using System.Reflection; 10 using System.Runtime.CompilerServices; 11 12 namespace System.Linq.Expressions.Interpreter 13 { 14 internal abstract class OffsetInstruction : Instruction 15 { 16 internal const int Unknown = int.MinValue; 17 internal const int CacheSize = 32; 18 19 // the offset to jump to (relative to this instruction): 20 protected int _offset = Unknown; 21 22 public abstract Instruction[] Cache { get; } 23 Fixup(int offset)24 public Instruction Fixup(int offset) 25 { 26 Debug.Assert(_offset == Unknown && offset != Unknown); 27 _offset = offset; 28 29 Instruction[] cache = Cache; 30 if (cache != null && offset >= 0 && offset < cache.Length) 31 { 32 return cache[offset] ?? (cache[offset] = this); 33 } 34 35 return this; 36 } 37 ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IReadOnlyList<object> objects)38 public override string ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IReadOnlyList<object> objects) 39 { 40 return ToString() + (_offset != Unknown ? " -> " + (instructionIndex + _offset) : ""); 41 } 42 ToString()43 public override string ToString() 44 { 45 return InstructionName + (_offset == Unknown ? "(?)" : "(" + _offset + ")"); 46 } 47 } 48 49 internal sealed class BranchFalseInstruction : OffsetInstruction 50 { 51 private static Instruction[] s_cache; 52 53 public override Instruction[] Cache 54 { 55 get 56 { 57 if (s_cache == null) 58 { 59 s_cache = new Instruction[CacheSize]; 60 } 61 return s_cache; 62 } 63 } 64 65 public override string InstructionName => "BranchFalse"; 66 public override int ConsumedStack => 1; 67 Run(InterpretedFrame frame)68 public override int Run(InterpretedFrame frame) 69 { 70 Debug.Assert(_offset != Unknown); 71 72 if (!(bool)frame.Pop()) 73 { 74 return _offset; 75 } 76 77 return 1; 78 } 79 } 80 81 internal sealed class BranchTrueInstruction : OffsetInstruction 82 { 83 private static Instruction[] s_cache; 84 85 public override Instruction[] Cache 86 { 87 get 88 { 89 if (s_cache == null) 90 { 91 s_cache = new Instruction[CacheSize]; 92 } 93 return s_cache; 94 } 95 } 96 97 public override string InstructionName => "BranchTrue"; 98 public override int ConsumedStack => 1; 99 Run(InterpretedFrame frame)100 public override int Run(InterpretedFrame frame) 101 { 102 Debug.Assert(_offset != Unknown); 103 104 if ((bool)frame.Pop()) 105 { 106 return _offset; 107 } 108 109 return 1; 110 } 111 } 112 113 internal sealed class CoalescingBranchInstruction : OffsetInstruction 114 { 115 private static Instruction[] s_cache; 116 117 public override Instruction[] Cache 118 { 119 get 120 { 121 if (s_cache == null) 122 { 123 s_cache = new Instruction[CacheSize]; 124 } 125 return s_cache; 126 } 127 } 128 129 public override string InstructionName => "CoalescingBranch"; 130 public override int ConsumedStack => 1; 131 public override int ProducedStack => 1; 132 Run(InterpretedFrame frame)133 public override int Run(InterpretedFrame frame) 134 { 135 Debug.Assert(_offset != Unknown); 136 137 if (frame.Peek() != null) 138 { 139 return _offset; 140 } 141 142 return 1; 143 } 144 } 145 146 internal class BranchInstruction : OffsetInstruction 147 { 148 private static Instruction[][][] s_caches; 149 150 public override Instruction[] Cache 151 { 152 get 153 { 154 if (s_caches == null) 155 { 156 s_caches = new Instruction[2][][] { new Instruction[2][], new Instruction[2][] }; 157 } 158 return s_caches[ConsumedStack][ProducedStack] ?? (s_caches[ConsumedStack][ProducedStack] = new Instruction[CacheSize]); 159 } 160 } 161 162 internal readonly bool _hasResult; 163 internal readonly bool _hasValue; 164 BranchInstruction()165 internal BranchInstruction() 166 : this(false, false) 167 { 168 } 169 BranchInstruction(bool hasResult, bool hasValue)170 public BranchInstruction(bool hasResult, bool hasValue) 171 { 172 _hasResult = hasResult; 173 _hasValue = hasValue; 174 } 175 176 public override string InstructionName => "Branch"; 177 public override int ConsumedStack => _hasValue ? 1 : 0; 178 public override int ProducedStack => _hasResult ? 1 : 0; 179 Run(InterpretedFrame frame)180 public override int Run(InterpretedFrame frame) 181 { 182 Debug.Assert(_offset != Unknown); 183 184 return _offset; 185 } 186 } 187 188 internal abstract class IndexedBranchInstruction : Instruction 189 { 190 protected const int CacheSize = 32; 191 internal readonly int _labelIndex; 192 IndexedBranchInstruction(int labelIndex)193 public IndexedBranchInstruction(int labelIndex) 194 { 195 _labelIndex = labelIndex; 196 } 197 GetLabel(InterpretedFrame frame)198 public RuntimeLabel GetLabel(InterpretedFrame frame) 199 { 200 Debug.Assert(_labelIndex != UnknownInstrIndex); 201 return frame.Interpreter._labels[_labelIndex]; 202 } 203 ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IReadOnlyList<object> objects)204 public override string ToDebugString(int instructionIndex, object cookie, Func<int, int> labelIndexer, IReadOnlyList<object> objects) 205 { 206 Debug.Assert(_labelIndex != UnknownInstrIndex); 207 int targetIndex = labelIndexer(_labelIndex); 208 return ToString() + (targetIndex != BranchLabel.UnknownIndex ? " -> " + targetIndex : ""); 209 } 210 ToString()211 public override string ToString() 212 { 213 Debug.Assert(_labelIndex != UnknownInstrIndex); 214 return InstructionName + "[" + _labelIndex + "]"; 215 } 216 } 217 218 /// <summary> 219 /// This instruction implements a goto expression that can jump out of any expression. 220 /// It pops values (arguments) from the evaluation stack that the expression tree nodes in between 221 /// the goto expression and the target label node pushed and not consumed yet. 222 /// A goto expression can jump into a node that evaluates arguments only if it carries 223 /// a value and jumps right after the first argument (the carried value will be used as the first argument). 224 /// Goto can jump into an arbitrary child of a BlockExpression since the block doesn't accumulate values 225 /// on evaluation stack as its child expressions are being evaluated. 226 /// 227 /// Goto needs to execute any finally blocks on the way to the target label. 228 /// <example> 229 /// { 230 /// f(1, 2, try { g(3, 4, try { goto L } finally { ... }, 6) } finally { ... }, 7, 8) 231 /// L: ... 232 /// } 233 /// </example> 234 /// The goto expression here jumps to label L while having 4 items on evaluation stack (1, 2, 3 and 4). 235 /// The jump needs to execute both finally blocks, the first one on stack level 4 the 236 /// second one on stack level 2. So, it needs to jump the first finally block, pop 2 items from the stack, 237 /// run second finally block and pop another 2 items from the stack and set instruction pointer to label L. 238 /// 239 /// Goto also needs to rethrow ThreadAbortException iff it jumps out of a catch handler and 240 /// the current thread is in "abort requested" state. 241 /// </summary> 242 internal sealed class GotoInstruction : IndexedBranchInstruction 243 { 244 private const int Variants = 8; 245 private static readonly GotoInstruction[] s_cache = new GotoInstruction[Variants * CacheSize]; 246 247 public override string InstructionName => "Goto"; 248 249 private readonly bool _hasResult; 250 251 private readonly bool _hasValue; 252 private readonly bool _labelTargetGetsValue; 253 254 // Should technically return 1 for ConsumedContinuations and ProducedContinuations for gotos that target a label whose continuation depth 255 // is different from the current continuation depth. This is because we will consume one continuation from the _continuations 256 // and at meantime produce a new _pendingContinuation. However, in case of forward gotos, we don't not know that is the 257 // case until the label is emitted. By then the consumed and produced stack information is useless. 258 // The important thing here is that the stack balance is 0. 259 260 public override int ConsumedStack => _hasValue ? 1 : 0; 261 public override int ProducedStack => _hasResult ? 1 : 0; 262 GotoInstruction(int targetIndex, bool hasResult, bool hasValue, bool labelTargetGetsValue)263 private GotoInstruction(int targetIndex, bool hasResult, bool hasValue, bool labelTargetGetsValue) 264 : base(targetIndex) 265 { 266 _hasResult = hasResult; 267 _hasValue = hasValue; 268 _labelTargetGetsValue = labelTargetGetsValue; 269 } 270 Create(int labelIndex, bool hasResult, bool hasValue, bool labelTargetGetsValue)271 internal static GotoInstruction Create(int labelIndex, bool hasResult, bool hasValue, bool labelTargetGetsValue) 272 { 273 if (labelIndex < CacheSize) 274 { 275 int index = Variants * labelIndex | (labelTargetGetsValue ? 4 : 0) | (hasResult ? 2 : 0) | (hasValue ? 1 : 0); 276 return s_cache[index] ?? (s_cache[index] = new GotoInstruction(labelIndex, hasResult, hasValue, labelTargetGetsValue)); 277 } 278 return new GotoInstruction(labelIndex, hasResult, hasValue, labelTargetGetsValue); 279 } 280 Run(InterpretedFrame frame)281 public override int Run(InterpretedFrame frame) 282 { 283 // Are we jumping out of catch/finally while aborting the current thread? 284 #if FEATURE_THREAD_ABORT 285 Interpreter.AbortThreadIfRequested(frame, _labelIndex); 286 #endif 287 288 // goto the target label or the current finally continuation: 289 object value = _hasValue ? frame.Pop() : Interpreter.NoValue; 290 return frame.Goto(_labelIndex, _labelTargetGetsValue ? value : Interpreter.NoValue, gotoExceptionHandler: false); 291 } 292 } 293 294 internal sealed class EnterTryCatchFinallyInstruction : IndexedBranchInstruction 295 { 296 private readonly bool _hasFinally = false; 297 private TryCatchFinallyHandler _tryHandler; 298 SetTryHandler(TryCatchFinallyHandler tryHandler)299 internal void SetTryHandler(TryCatchFinallyHandler tryHandler) 300 { 301 Debug.Assert(_tryHandler == null && tryHandler != null, "the tryHandler can be set only once"); 302 _tryHandler = tryHandler; 303 } 304 305 internal TryCatchFinallyHandler Handler => _tryHandler; 306 307 public override int ProducedContinuations => _hasFinally ? 1 : 0; 308 EnterTryCatchFinallyInstruction(int targetIndex, bool hasFinally)309 private EnterTryCatchFinallyInstruction(int targetIndex, bool hasFinally) 310 : base(targetIndex) 311 { 312 _hasFinally = hasFinally; 313 } 314 CreateTryFinally(int labelIndex)315 internal static EnterTryCatchFinallyInstruction CreateTryFinally(int labelIndex) 316 { 317 return new EnterTryCatchFinallyInstruction(labelIndex, true); 318 } 319 CreateTryCatch()320 internal static EnterTryCatchFinallyInstruction CreateTryCatch() 321 { 322 return new EnterTryCatchFinallyInstruction(UnknownInstrIndex, false); 323 } 324 Run(InterpretedFrame frame)325 public override int Run(InterpretedFrame frame) 326 { 327 Debug.Assert(_tryHandler != null, "the tryHandler must be set already"); 328 329 if (_hasFinally) 330 { 331 // Push finally. 332 frame.PushContinuation(_labelIndex); 333 } 334 int prevInstrIndex = frame.InstructionIndex; 335 frame.InstructionIndex++; 336 337 // Start to run the try/catch/finally blocks 338 Instruction[] instructions = frame.Interpreter.Instructions.Instructions; 339 ExceptionHandler exHandler; 340 object unwrappedException; 341 try 342 { 343 // run the try block 344 int index = frame.InstructionIndex; 345 while (index >= _tryHandler.TryStartIndex && index < _tryHandler.TryEndIndex) 346 { 347 index += instructions[index].Run(frame); 348 frame.InstructionIndex = index; 349 } 350 351 // we finish the try block and is about to jump out of the try/catch blocks 352 if (index == _tryHandler.GotoEndTargetIndex) 353 { 354 // run the 'Goto' that jumps out of the try/catch/finally blocks 355 Debug.Assert(instructions[index] is GotoInstruction, "should be the 'Goto' instruction that jumps out the try/catch/finally"); 356 frame.InstructionIndex += instructions[index].Run(frame); 357 } 358 } 359 catch (Exception exception) when (_tryHandler.HasHandler(frame, exception, out exHandler, out unwrappedException)) 360 { 361 Debug.Assert(!(unwrappedException is RethrowException)); 362 frame.InstructionIndex += frame.Goto(exHandler.LabelIndex, unwrappedException, gotoExceptionHandler: true); 363 364 #if FEATURE_THREAD_ABORT 365 // stay in the current catch so that ThreadAbortException is not rethrown by CLR: 366 var abort = exception as ThreadAbortException; 367 if (abort != null) 368 { 369 Interpreter.AnyAbortException = abort; 370 frame.CurrentAbortHandler = exHandler; 371 } 372 #endif 373 374 bool rethrow = false; 375 try 376 { 377 // run the catch block 378 int index = frame.InstructionIndex; 379 while (index >= exHandler.HandlerStartIndex && index < exHandler.HandlerEndIndex) 380 { 381 index += instructions[index].Run(frame); 382 frame.InstructionIndex = index; 383 } 384 385 // we finish the catch block and is about to jump out of the try/catch blocks 386 if (index == _tryHandler.GotoEndTargetIndex) 387 { 388 // run the 'Goto' that jumps out of the try/catch/finally blocks 389 Debug.Assert(instructions[index] is GotoInstruction, "should be the 'Goto' instruction that jumps out the try/catch/finally"); 390 frame.InstructionIndex += instructions[index].Run(frame); 391 } 392 } 393 catch (RethrowException) 394 { 395 // a rethrow instruction in a catch block gets to run 396 rethrow = true; 397 } 398 399 if (rethrow) { throw; } 400 } 401 finally 402 { 403 if (_tryHandler.IsFinallyBlockExist) 404 { 405 // We get to the finally block in two paths: 406 // 1. Jump from the try/catch blocks. This includes two sub-routes: 407 // a. 'Goto' instruction in the middle of try/catch block 408 // b. try/catch block runs to its end. Then the 'Goto(end)' will be trigger to jump out of the try/catch block 409 // 2. Exception thrown from the try/catch blocks 410 // In the first path, the continuation mechanism works and frame.InstructionIndex will be updated to point to the first instruction of the finally block 411 // In the second path, the continuation mechanism is not involved and frame.InstructionIndex is not updated 412 #if DEBUG 413 bool isFromJump = frame.IsJumpHappened(); 414 Debug.Assert(!isFromJump || (isFromJump && _tryHandler.FinallyStartIndex == frame.InstructionIndex), "we should already jump to the first instruction of the finally"); 415 #endif 416 // run the finally block 417 // we cannot jump out of the finally block, and we cannot have an immediate rethrow in it 418 int index = frame.InstructionIndex = _tryHandler.FinallyStartIndex; 419 while (index >= _tryHandler.FinallyStartIndex && index < _tryHandler.FinallyEndIndex) 420 { 421 index += instructions[index].Run(frame); 422 frame.InstructionIndex = index; 423 } 424 } 425 } 426 427 return frame.InstructionIndex - prevInstrIndex; 428 } 429 430 public override string InstructionName => _hasFinally ? "EnterTryFinally" : "EnterTryCatch"; 431 ToString()432 public override string ToString() => _hasFinally ? "EnterTryFinally[" + _labelIndex + "]" : "EnterTryCatch"; 433 } 434 435 internal sealed class EnterTryFaultInstruction : IndexedBranchInstruction 436 { 437 private TryFaultHandler _tryHandler; 438 EnterTryFaultInstruction(int targetIndex)439 internal EnterTryFaultInstruction(int targetIndex) 440 : base(targetIndex) 441 { 442 } 443 444 public override string InstructionName => "EnterTryFault"; 445 public override int ProducedContinuations => 1; 446 447 internal TryFaultHandler Handler => _tryHandler; 448 SetTryHandler(TryFaultHandler tryHandler)449 internal void SetTryHandler(TryFaultHandler tryHandler) 450 { 451 Debug.Assert(tryHandler != null); 452 Debug.Assert(_tryHandler == null, "the tryHandler can be set only once"); 453 _tryHandler = tryHandler; 454 } 455 Run(InterpretedFrame frame)456 public override int Run(InterpretedFrame frame) 457 { 458 Debug.Assert(_tryHandler != null, "the tryHandler must be set already"); 459 460 // Push fault. 461 frame.PushContinuation(_labelIndex); 462 463 int prevInstrIndex = frame.InstructionIndex; 464 frame.InstructionIndex++; 465 466 // Start to run the try/fault blocks 467 Instruction[] instructions = frame.Interpreter.Instructions.Instructions; 468 469 // C# 6 has no direct support for fault blocks, but they can be faked or coerced out of the compiler 470 // in several ways. Catch-and-rethrow can work in specific cases, but not generally as the double-pass 471 // will not work correctly with filters higher up the call stack. Iterators can be used to produce real 472 // fault blocks, but it depends on an implementation detail rather than a guarantee, and is rather 473 // indirect. This leaves using a finally block and not doing anything in it if the body ran to 474 // completion, which is the approach used here. 475 bool ranWithoutFault = false; 476 try 477 { 478 // run the try block 479 int index = frame.InstructionIndex; 480 while (index >= _tryHandler.TryStartIndex && index < _tryHandler.TryEndIndex) 481 { 482 index += instructions[index].Run(frame); 483 frame.InstructionIndex = index; 484 } 485 486 // run the 'Goto' that jumps out of the try/fault blocks 487 Debug.Assert(instructions[index] is GotoInstruction, "should be the 'Goto' instruction that jumps out the try/fault"); 488 489 // if we've arrived here there was no exception thrown. As the fault block won't run, we need to 490 // pop the continuation for it here, before Gotoing the end of the try/fault. 491 ranWithoutFault = true; 492 frame.RemoveContinuation(); 493 frame.InstructionIndex += instructions[index].Run(frame); 494 } 495 finally 496 { 497 if (!ranWithoutFault) 498 { 499 // run the fault block 500 // we cannot jump out of the finally block, and we cannot have an immediate rethrow in it 501 int index = frame.InstructionIndex = _tryHandler.FinallyStartIndex; 502 while (index >= _tryHandler.FinallyStartIndex && index < _tryHandler.FinallyEndIndex) 503 { 504 index += instructions[index].Run(frame); 505 frame.InstructionIndex = index; 506 } 507 } 508 } 509 510 return frame.InstructionIndex - prevInstrIndex; 511 } 512 } 513 514 /// <summary> 515 /// The first instruction of finally block. 516 /// </summary> 517 internal sealed class EnterFinallyInstruction : IndexedBranchInstruction 518 { 519 private static readonly EnterFinallyInstruction[] s_cache = new EnterFinallyInstruction[CacheSize]; 520 EnterFinallyInstruction(int labelIndex)521 private EnterFinallyInstruction(int labelIndex) 522 : base(labelIndex) 523 { 524 } 525 526 public override string InstructionName => "EnterFinally"; 527 public override int ProducedStack => 2; 528 public override int ConsumedContinuations => 1; 529 Create(int labelIndex)530 internal static EnterFinallyInstruction Create(int labelIndex) 531 { 532 if (labelIndex < CacheSize) 533 { 534 return s_cache[labelIndex] ?? (s_cache[labelIndex] = new EnterFinallyInstruction(labelIndex)); 535 } 536 return new EnterFinallyInstruction(labelIndex); 537 } 538 Run(InterpretedFrame frame)539 public override int Run(InterpretedFrame frame) 540 { 541 // If _pendingContinuation == -1 then we were getting into the finally block because an exception was thrown 542 // in this case we need to set the stack depth 543 // Else we were getting into this finally block from a 'Goto' jump, and the stack depth is already set properly 544 if (!frame.IsJumpHappened()) 545 { 546 frame.SetStackDepth(GetLabel(frame).StackDepth); 547 } 548 549 frame.PushPendingContinuation(); 550 frame.RemoveContinuation(); 551 return 1; 552 } 553 } 554 555 /// <summary> 556 /// The last instruction of finally block. 557 /// </summary> 558 internal sealed class LeaveFinallyInstruction : Instruction 559 { 560 internal static readonly Instruction Instance = new LeaveFinallyInstruction(); 561 LeaveFinallyInstruction()562 private LeaveFinallyInstruction() { } 563 564 public override int ConsumedStack => 2; 565 public override string InstructionName => "LeaveFinally"; 566 Run(InterpretedFrame frame)567 public override int Run(InterpretedFrame frame) 568 { 569 frame.PopPendingContinuation(); 570 571 // If _pendingContinuation == -1 then we were getting into the finally block because an exception was thrown 572 // In this case we just return 1, and the real instruction index will be calculated by GotoHandler later 573 if (!frame.IsJumpHappened()) { return 1; } 574 // jump to goto target or to the next finally: 575 return frame.YieldToPendingContinuation(); 576 } 577 } 578 579 internal sealed class EnterFaultInstruction : IndexedBranchInstruction 580 { 581 private static readonly EnterFaultInstruction[] s_cache = new EnterFaultInstruction[CacheSize]; 582 EnterFaultInstruction(int labelIndex)583 private EnterFaultInstruction(int labelIndex) 584 : base(labelIndex) 585 { 586 } 587 588 public override string InstructionName => "EnterFault"; 589 public override int ProducedStack => 2; 590 Create(int labelIndex)591 internal static EnterFaultInstruction Create(int labelIndex) 592 { 593 if (labelIndex < CacheSize) 594 { 595 return s_cache[labelIndex] ?? (s_cache[labelIndex] = new EnterFaultInstruction(labelIndex)); 596 } 597 598 return new EnterFaultInstruction(labelIndex); 599 } 600 Run(InterpretedFrame frame)601 public override int Run(InterpretedFrame frame) 602 { 603 Debug.Assert(!frame.IsJumpHappened()); 604 605 frame.SetStackDepth(GetLabel(frame).StackDepth); 606 frame.PushPendingContinuation(); 607 frame.RemoveContinuation(); 608 return 1; 609 } 610 } 611 612 internal sealed class LeaveFaultInstruction : Instruction 613 { 614 internal static readonly Instruction Instance = new LeaveFaultInstruction(); 615 LeaveFaultInstruction()616 private LeaveFaultInstruction() { } 617 618 public override int ConsumedStack => 2; 619 public override int ConsumedContinuations => 1; 620 public override string InstructionName => "LeaveFault"; 621 Run(InterpretedFrame frame)622 public override int Run(InterpretedFrame frame) 623 { 624 frame.PopPendingContinuation(); 625 626 Debug.Assert(!frame.IsJumpHappened()); 627 // Just return 1, and the real instruction index will be calculated by GotoHandler later 628 return 1; 629 } 630 } 631 632 // no-op: we need this just to balance the stack depth and aid debugging of the instruction list. 633 internal sealed class EnterExceptionFilterInstruction : Instruction 634 { 635 internal static readonly EnterExceptionFilterInstruction Instance = new EnterExceptionFilterInstruction(); 636 EnterExceptionFilterInstruction()637 private EnterExceptionFilterInstruction() { } 638 639 public override string InstructionName => "EnterExceptionFilter"; 640 641 642 // The exception is pushed onto the stack in the filter runner. 643 public override int ProducedStack => 1; 644 645 [ExcludeFromCodeCoverage] // Known to be a no-op, this instruction is skipped on execution. 646 public override int Run(InterpretedFrame frame) => 1; 647 } 648 649 // no-op: we need this just to balance the stack depth and aid debugging of the instruction list. 650 internal sealed class LeaveExceptionFilterInstruction : Instruction 651 { 652 internal static readonly LeaveExceptionFilterInstruction Instance = new LeaveExceptionFilterInstruction(); 653 LeaveExceptionFilterInstruction()654 private LeaveExceptionFilterInstruction() { } 655 656 public override string InstructionName => "LeaveExceptionFilter"; 657 658 // The exception and the boolean result are popped from the stack in the filter runner. 659 public override int ConsumedStack => 2; 660 661 [ExcludeFromCodeCoverage] // Known to be a no-op, this instruction is skipped on execution. 662 public override int Run(InterpretedFrame frame) => 1; 663 } 664 665 // no-op: we need this just to balance the stack depth. 666 internal sealed class EnterExceptionHandlerInstruction : Instruction 667 { 668 internal static readonly EnterExceptionHandlerInstruction Void = new EnterExceptionHandlerInstruction(false); 669 internal static readonly EnterExceptionHandlerInstruction NonVoid = new EnterExceptionHandlerInstruction(true); 670 671 // True if try-expression is non-void. 672 private readonly bool _hasValue; 673 EnterExceptionHandlerInstruction(bool hasValue)674 private EnterExceptionHandlerInstruction(bool hasValue) 675 { 676 _hasValue = hasValue; 677 } 678 679 public override string InstructionName => "EnterExceptionHandler"; 680 681 // If an exception is throws in try-body the expression result of try-body is not evaluated and loaded to the stack. 682 // So the stack doesn't contain the try-body's value when we start executing the handler. 683 // However, while emitting instructions try block falls thru the catch block with a value on stack. 684 // We need to declare it consumed so that the stack state upon entry to the handler corresponds to the real 685 // stack depth after throw jumped to this catch block. 686 public override int ConsumedStack => _hasValue ? 1 : 0; 687 688 // A variable storing the current exception is pushed to the stack by exception handling. 689 // Catch handlers: The value is immediately popped and stored into a local. 690 public override int ProducedStack => 1; 691 692 [ExcludeFromCodeCoverage] // Known to be a no-op, this instruction is skipped on execution. Run(InterpretedFrame frame)693 public override int Run(InterpretedFrame frame) 694 { 695 // nop (the exception value is pushed by the interpreter in HandleCatch) 696 return 1; 697 } 698 } 699 700 /// <summary> 701 /// The last instruction of a catch exception handler. 702 /// </summary> 703 internal sealed class LeaveExceptionHandlerInstruction : IndexedBranchInstruction 704 { 705 private static readonly LeaveExceptionHandlerInstruction[] s_cache = new LeaveExceptionHandlerInstruction[2 * CacheSize]; 706 707 private readonly bool _hasValue; 708 LeaveExceptionHandlerInstruction(int labelIndex, bool hasValue)709 private LeaveExceptionHandlerInstruction(int labelIndex, bool hasValue) 710 : base(labelIndex) 711 { 712 _hasValue = hasValue; 713 } 714 715 public override string InstructionName => "LeaveExceptionHandler"; 716 717 // The catch block yields a value if the body is non-void. This value is left on the stack. 718 public override int ConsumedStack => _hasValue ? 1 : 0; 719 public override int ProducedStack => _hasValue ? 1 : 0; 720 Create(int labelIndex, bool hasValue)721 internal static LeaveExceptionHandlerInstruction Create(int labelIndex, bool hasValue) 722 { 723 if (labelIndex < CacheSize) 724 { 725 int index = (2 * labelIndex) | (hasValue ? 1 : 0); 726 return s_cache[index] ?? (s_cache[index] = new LeaveExceptionHandlerInstruction(labelIndex, hasValue)); 727 } 728 return new LeaveExceptionHandlerInstruction(labelIndex, hasValue); 729 } 730 Run(InterpretedFrame frame)731 public override int Run(InterpretedFrame frame) 732 { 733 // CLR rethrows ThreadAbortException when leaving catch handler if abort is requested on the current thread. 734 #if FEATURE_THREAD_ABORT 735 Interpreter.AbortThreadIfRequested(frame, _labelIndex); 736 #endif 737 return GetLabel(frame).Index - frame.InstructionIndex; 738 } 739 } 740 741 internal sealed class ThrowInstruction : Instruction 742 { 743 internal static readonly ThrowInstruction Throw = new ThrowInstruction(true, false); 744 internal static readonly ThrowInstruction VoidThrow = new ThrowInstruction(false, false); 745 internal static readonly ThrowInstruction Rethrow = new ThrowInstruction(true, true); 746 internal static readonly ThrowInstruction VoidRethrow = new ThrowInstruction(false, true); 747 748 private readonly bool _hasResult, _rethrow; 749 ThrowInstruction(bool hasResult, bool isRethrow)750 private ThrowInstruction(bool hasResult, bool isRethrow) 751 { 752 _hasResult = hasResult; 753 _rethrow = isRethrow; 754 } 755 756 public override string InstructionName => "Throw"; 757 public override int ProducedStack => _hasResult ? 1 : 0; 758 public override int ConsumedStack => 1; 759 Run(InterpretedFrame frame)760 public override int Run(InterpretedFrame frame) 761 { 762 Exception ex = WrapThrownObject(frame.Pop()); 763 if (_rethrow) 764 { 765 throw new RethrowException(); 766 } 767 768 throw ex; 769 } 770 771 private static Exception WrapThrownObject(object thrown) => 772 thrown == null ? null : (thrown as Exception ?? new RuntimeWrappedException(thrown)); 773 } 774 775 internal sealed class IntSwitchInstruction<T> : Instruction 776 { 777 private readonly Dictionary<T, int> _cases; 778 IntSwitchInstruction(Dictionary<T, int> cases)779 internal IntSwitchInstruction(Dictionary<T, int> cases) 780 { 781 Assert.NotNull(cases); 782 _cases = cases; 783 } 784 785 public override string InstructionName => "IntSwitch"; 786 public override int ConsumedStack => 1; 787 Run(InterpretedFrame frame)788 public override int Run(InterpretedFrame frame) 789 { 790 int target; 791 return _cases.TryGetValue((T)frame.Pop(), out target) ? target : 1; 792 } 793 } 794 795 internal sealed class StringSwitchInstruction : Instruction 796 { 797 private readonly Dictionary<string, int> _cases; 798 private readonly StrongBox<int> _nullCase; 799 StringSwitchInstruction(Dictionary<string, int> cases, StrongBox<int> nullCase)800 internal StringSwitchInstruction(Dictionary<string, int> cases, StrongBox<int> nullCase) 801 { 802 Assert.NotNull(cases); 803 Assert.NotNull(nullCase); 804 _cases = cases; 805 _nullCase = nullCase; 806 } 807 808 public override string InstructionName => "StringSwitch"; 809 public override int ConsumedStack => 1; 810 Run(InterpretedFrame frame)811 public override int Run(InterpretedFrame frame) 812 { 813 object value = frame.Pop(); 814 815 if (value == null) 816 { 817 return _nullCase.Value; 818 } 819 820 int target; 821 return _cases.TryGetValue((string)value, out target) ? target : 1; 822 } 823 } 824 } 825