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