mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							847 lines
						
					
					
						
							29 KiB
						
					
					
				
			
		
		
	
	
							847 lines
						
					
					
						
							29 KiB
						
					
					
				// Copyright (c) 2016 Daniel Grunwald | 
						|
//  | 
						|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this | 
						|
// software and associated documentation files (the "Software"), to deal in the Software | 
						|
// without restriction, including without limitation the rights to use, copy, modify, merge, | 
						|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | 
						|
// to whom the Software is furnished to do so, subject to the following conditions: | 
						|
//  | 
						|
// The above copyright notice and this permission notice shall be included in all copies or | 
						|
// substantial portions of the Software. | 
						|
//  | 
						|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | 
						|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | 
						|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | 
						|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | 
						|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | 
						|
// DEALINGS IN THE SOFTWARE. | 
						|
 | 
						|
using System; | 
						|
using System.Collections.Generic; | 
						|
using System.Diagnostics; | 
						|
 | 
						|
using ICSharpCode.Decompiler.IL; | 
						|
 | 
						|
namespace ICSharpCode.Decompiler.FlowAnalysis | 
						|
{ | 
						|
	/// <summary> | 
						|
	/// Interface for use with DataFlowVisitor. | 
						|
	///  | 
						|
	/// A mutable container for the state tracked by the data flow analysis. | 
						|
	/// </summary> | 
						|
	/// <remarks> | 
						|
	/// States must form a join-semilattice: https://en.wikipedia.org/wiki/Semilattice | 
						|
	///  | 
						|
	/// To handle <c>try{} finally{}</c> properly, states should implement <c>MeetWith()</c> as well, | 
						|
	/// and thus should form a lattice. | 
						|
	///  | 
						|
	/// <c>DataFlowVisitor</c> expects the state to behave like a mutable reference type. | 
						|
	/// It might still be a good idea to use a struct to implement it so that .NET uses static dispatch for | 
						|
	/// method calls on the type parameter, but that struct must consist only of a <c>readonly</c> field | 
						|
	/// referencing some mutable object, to ensure the type parameter behaves as it if was a mutable reference type. | 
						|
	/// </remarks> | 
						|
	public interface IDataFlowState<Self> where Self : IDataFlowState<Self> | 
						|
	{ | 
						|
		/// <summary> | 
						|
		/// Gets whether this state is "less than" (or equal to) another state. | 
						|
		/// This is the partial order of the semi-lattice. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// The exact meaning of this relation is up to the concrete implementation, | 
						|
		/// but usually "less than" means "has less information than". | 
						|
		/// A given position in the code starts at the "bottom state" (=no information) | 
						|
		/// and then adds more information as the analysis progresses. | 
						|
		/// After each change to the state, the old state must be less than the new state, | 
						|
		/// so that the analysis does not run into an infinite loop. | 
						|
		/// The partially ordered set must also have finite height (no infinite ascending chains s1 < s2 < ...), | 
						|
		/// to ensure the analysis terminates. | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simplest possible non-trivial state, <c>bool isReachable</c>, would implement <c>LessThanOrEqual</c> as: | 
						|
		/// <code>return (this.isReachable ? 1 : 0) <= (otherState.isReachable ? 1 : 0);</code> | 
						|
		/// <para>Which can be simpified to:</para> | 
						|
		/// <code>return !this.isReachable || otherState.isReachable;</code> | 
						|
		/// </example> | 
						|
		bool LessThanOrEqual(Self otherState); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Creates a new object with a copy of the state. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// Mutating methods such as <c>ReplaceWith</c> or <c>JoinWith</c> modify the contents of a state object. | 
						|
		/// Cloning the object allows the analysis to track multiple independent states, | 
						|
		/// such as the | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>Clone</c> as: | 
						|
		/// <code>return new MyState(this.isReachable);</code> | 
						|
		/// </example> | 
						|
		Self Clone(); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Replace the contents of this state object with a copy of those in <paramref name="newContent"/>. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// <c>x = x.Clone(); x.ReplaceWith(newContent);</c> | 
						|
		/// is equivalent to | 
						|
		/// <c>x = newContent.Clone();</c> | 
						|
		///  | 
						|
		/// ReplaceWith() is used to avoid allocating new state objects where possible. | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWith</c> as: | 
						|
		/// <code>this.isReachable = newContent.isReachable;</code> | 
						|
		/// </example> | 
						|
		void ReplaceWith(Self newContent); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Join the incomingState into this state. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// Postcondition: <c>old(this).LessThanOrEqual(this) && incomingState.LessThanOrEqual(this)</c> | 
						|
		/// This method should set <c>this</c> to the smallest state that is greater than (or equal to) | 
						|
		/// both input states. | 
						|
		///  | 
						|
		/// <c>JoinWith()</c> is used when multiple control flow paths are joined together. | 
						|
		/// For example, it is used to combine the <c>thenState</c> with the <c>elseState</c> | 
						|
		/// at the end of a if-else construct. | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>JoinWith</c> as: | 
						|
		/// <code>this.isReachable |= incomingState.isReachable;</code> | 
						|
		/// </example> | 
						|
		void JoinWith(Self incomingState); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// A special operation to merge the end-state of the finally-block with the end state of | 
						|
		/// a branch leaving the try-block. | 
						|
		///  | 
						|
		/// If either input state is unreachable, this call must result in an unreachable state. | 
						|
		/// </summary> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>TriggerFinally</c> as: | 
						|
		/// <code>this.isReachable &= finallyState.isReachable;</code> | 
						|
		/// </example> | 
						|
		void TriggerFinally(Self finallyState); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets whether this is the bottom state. | 
						|
		///  | 
						|
		/// The bottom state represents that the data flow analysis has not yet | 
						|
		/// found a code path from the entry point to this state's position. | 
						|
		/// It thus contains no information, and is "less than" all other states. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// The bottom state is the bottom element in the semi-lattice. | 
						|
		///  | 
						|
		/// Initially, all code blocks not yet visited by the analysis will be in the bottom state. | 
						|
		/// Unreachable code will always remain in the bottom state. | 
						|
		/// Some analyses may also use the bottom state for reachable code after it was processed by the analysis. | 
						|
		/// For example, in <c>DefiniteAssignmentVisitor</c> the bottom states means | 
						|
		/// "either this code is unreachable, or all variables are definitely initialized". | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>IsBottom</c> as: | 
						|
		/// <code>return !this.isReachable;</code> | 
						|
		/// </example> | 
						|
		bool IsBottom { get; } | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Equivalent to <c>this.ReplaceWith(bottomState)</c>, but may be implemented more efficiently. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// Since the <c>DataFlowVisitor</c> can only create states by cloning from the initial state, | 
						|
		/// this method is necessary for the <c>DataFlowVisitor</c> to gain access to the bottom element in | 
						|
		/// the first place. | 
						|
		/// </remarks> | 
						|
		/// <example> | 
						|
		/// The simple state "<c>bool isReachable</c>", would implement <c>ReplaceWithBottom</c> as: | 
						|
		/// <code>this.isReachable = false;</code> | 
						|
		/// </example> | 
						|
		void ReplaceWithBottom(); | 
						|
	} | 
						|
 | 
						|
	/// <summary> | 
						|
	/// Generic base class for forward data flow analyses. | 
						|
	/// </summary> | 
						|
	/// <typeparam name="State"> | 
						|
	/// The state type used for the data flow analysis. See <see cref="IDataFlowState{Self}"/> for details. | 
						|
	/// </typeparam> | 
						|
	public abstract class DataFlowVisitor<State> : ILVisitor | 
						|
		where State : IDataFlowState<State> | 
						|
	{ | 
						|
		// The data flow analysis tracks a 'state'. | 
						|
		// There are many states (one per source code position, i.e. ILInstruction), but we don't store all of them. | 
						|
		// We only keep track of: | 
						|
		//  a) the current state in the RDVisitor | 
						|
		//     This state corresponds to the instruction currently being visited, | 
						|
		//     and gets mutated as we traverse the ILAst. | 
						|
		//  b) the input state for each control flow node | 
						|
		//     These also gets mutated as the analysis learns about new control flow edges. | 
						|
 | 
						|
		/// <summary> | 
						|
		/// The bottom state. | 
						|
		/// Must not be mutated. | 
						|
		/// </summary> | 
						|
		State bottomState; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Current state. | 
						|
		///  | 
						|
		/// Caution: any state object assigned to this member gets mutated as the visitor traverses the ILAst! | 
						|
		/// </summary> | 
						|
		protected State state; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Combined state of all possible exceptional control flow paths in the current try block. | 
						|
		/// Serves as input state for catch blocks. | 
						|
		///  | 
						|
		/// Caution: any state object assigned to this member gets mutated as the visitor encounters instructions that may throw exceptions! | 
						|
		///  | 
						|
		/// Within a try block, <c>currentStateOnException == stateOnException[tryBlock.Parent]</c>. | 
						|
		/// </summary> | 
						|
		/// <seealso cref="PropagateStateOnException"/> | 
						|
		protected State currentStateOnException; | 
						|
 | 
						|
		bool initialized; | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Initializes the DataFlowVisitor. | 
						|
		/// This method must be called once before any Visit()-methods can be called. | 
						|
		/// It must not be called more than once. | 
						|
		/// </summary> | 
						|
		/// <param name="initialState">The initial state at the entry point of the analysis.</param> | 
						|
		/// <remarks> | 
						|
		/// This is a method instead of a constructor because derived classes might need complex initialization | 
						|
		/// before they can construct the initial state. | 
						|
		/// </remarks> | 
						|
		protected void Initialize(State initialState) | 
						|
		{ | 
						|
			Debug.Assert(!initialized); | 
						|
			initialized = true; | 
						|
			this.state = initialState.Clone(); | 
						|
			this.bottomState = initialState.Clone(); | 
						|
			this.bottomState.ReplaceWithBottom(); | 
						|
			Debug.Assert(bottomState.IsBottom); | 
						|
			this.stateOnNullableRewrap = bottomState.Clone(); | 
						|
			this.currentStateOnException = state.Clone(); | 
						|
		} | 
						|
 | 
						|
#if DEBUG | 
						|
		// For debugging, capture the input + output state at every instruction. | 
						|
		readonly Dictionary<ILInstruction, State> debugInputState = new Dictionary<ILInstruction, State>(); | 
						|
		readonly Dictionary<ILInstruction, State> debugOutputState = new Dictionary<ILInstruction, State>(); | 
						|
 | 
						|
		void DebugPoint(Dictionary<ILInstruction, State> debugDict, ILInstruction inst) | 
						|
		{ | 
						|
#if DEBUG | 
						|
			Debug.Assert(initialized, "Initialize() was not called"); | 
						|
 | 
						|
			if (debugDict.TryGetValue(inst, out State previousState)) | 
						|
			{ | 
						|
				Debug.Assert(previousState.LessThanOrEqual(state)); | 
						|
				previousState.JoinWith(state); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				// limit the number of tracked instructions to make memory usage in debug builds less horrible | 
						|
				if (debugDict.Count < 1000) | 
						|
				{ | 
						|
					debugDict.Add(inst, state.Clone()); | 
						|
				} | 
						|
			} | 
						|
 | 
						|
			// currentStateOnException should be all states within the try block joined together | 
						|
			// -> state should already have been joined into currentStateOnException. | 
						|
			Debug.Assert(state.LessThanOrEqual(currentStateOnException)); | 
						|
#endif | 
						|
		} | 
						|
#endif | 
						|
 | 
						|
		[Conditional("DEBUG")] | 
						|
		protected void DebugStartPoint(ILInstruction inst) | 
						|
		{ | 
						|
#if DEBUG | 
						|
			DebugPoint(debugInputState, inst); | 
						|
#endif | 
						|
		} | 
						|
 | 
						|
		[Conditional("DEBUG")] | 
						|
		protected void DebugEndPoint(ILInstruction inst) | 
						|
		{ | 
						|
#if DEBUG | 
						|
			DebugPoint(debugOutputState, inst); | 
						|
#endif | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Derived classes may add to this set of flags to ensure they don't forget to override an interesting method. | 
						|
		/// </summary> | 
						|
		protected InstructionFlags flagsRequiringManualImpl = InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.MayUnwrapNull | InstructionFlags.EndPointUnreachable; | 
						|
 | 
						|
		protected sealed override void Default(ILInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			// This method assumes normal control flow and no branches. | 
						|
			if ((inst.DirectFlags & flagsRequiringManualImpl) != 0) | 
						|
			{ | 
						|
				throw new NotImplementedException(GetType().Name + " is missing implementation for " + inst.GetType().Name); | 
						|
			} | 
						|
 | 
						|
			// Since this instruction has normal control flow, we can evaluate our children left-to-right. | 
						|
			foreach (var child in inst.Children) | 
						|
			{ | 
						|
				child.AcceptVisitor(this); | 
						|
				Debug.Assert(state.IsBottom || !child.HasFlag(InstructionFlags.EndPointUnreachable), | 
						|
							 "Unreachable code must be in the bottom state."); | 
						|
			} | 
						|
 | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Handle control flow when the current instruction throws an exception: | 
						|
		/// joins the current state into the "exception state" of the current try block. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// This should not only be called for instructions that may throw an exception, | 
						|
		/// but for all instructions (due to async exceptions like ThreadAbortException)! | 
						|
		///  | 
						|
		/// To avoid redundant calls, every Visit() call may assume that the current state | 
						|
		/// is already propagated, and has to guarantee the same at the end. | 
						|
		/// This means this method should be called after every state change. | 
						|
		/// Alternatively, derived classes may directly modify both <c>state</c> | 
						|
		/// and <c>currentStateOnException</c>, so that a full <c>JoinWith()</c> call | 
						|
		/// is not necessary. | 
						|
		/// </remarks> | 
						|
		protected void PropagateStateOnException() | 
						|
		{ | 
						|
			currentStateOnException.JoinWith(state); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Replace the current state with the bottom state. | 
						|
		/// </summary> | 
						|
		protected void MarkUnreachable() | 
						|
		{ | 
						|
			state.ReplaceWithBottom(); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Holds the state for incoming branches. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// Only used for blocks in block containers; not for inline blocks. | 
						|
		/// </remarks> | 
						|
		readonly Dictionary<Block, State> stateOnBranch = new Dictionary<Block, State>(); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Holds the state at the block container end-point. (=state for incoming 'leave' instructions) | 
						|
		/// </summary> | 
						|
		readonly Dictionary<BlockContainer, State> stateOnLeave = new Dictionary<BlockContainer, State>(); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Gets the state object that holds the state for incoming branches to the block. | 
						|
		/// </summary> | 
						|
		/// <remarks> | 
						|
		/// Returns the a clone of the bottom state on the first call for a given block, | 
						|
		/// then returns the same object instance on further calls. | 
						|
		/// The caller is expected to mutate the returned state by calling <c>JoinWith()</c>. | 
						|
		/// </remarks> | 
						|
		State GetBlockInputState(Block block) | 
						|
		{ | 
						|
			if (stateOnBranch.TryGetValue(block, out State s)) | 
						|
			{ | 
						|
				return s; | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				s = bottomState.Clone(); | 
						|
				stateOnBranch.Add(block, s); | 
						|
				return s; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// For each block container, stores the set of blocks (via Block.ChildIndex) | 
						|
		/// that had their incoming state changed and were not processed yet. | 
						|
		/// </summary> | 
						|
		readonly Dictionary<BlockContainer, SortedSet<int>> workLists = new Dictionary<BlockContainer, SortedSet<int>>(); | 
						|
 | 
						|
		protected internal override void VisitBlockContainer(BlockContainer container) | 
						|
		{ | 
						|
			DebugStartPoint(container); | 
						|
			SortedSet<int> worklist = new SortedSet<int>(); | 
						|
			// register work list so that branches within this container can add to it | 
						|
			workLists.Add(container, worklist); | 
						|
			var stateOnEntry = GetBlockInputState(container.EntryPoint); | 
						|
			if (!state.LessThanOrEqual(stateOnEntry)) | 
						|
			{ | 
						|
				// If we have new information for the container's entry point, | 
						|
				// add the container entry point to the work list. | 
						|
				stateOnEntry.JoinWith(state); | 
						|
				worklist.Add(0); | 
						|
			} | 
						|
 | 
						|
			// To handle loops, we need to analyze the loop body before we can know the state for the loop backedge, | 
						|
			// but we need to know the input state for the loop body (to which the backedge state contributes) | 
						|
			// before we can analyze the loop body. | 
						|
			// Solution: we repeat the analysis of the loop body multiple times, until the state no longer changes. | 
						|
			// To make it terminate reasonably quickly, we need to process the control flow nodes in the correct order: | 
						|
			// reverse post-order. We use a SortedSet<int> for this, and assume that the block indices used in the SortedSet | 
						|
			// are ordered appropriately. The caller can use BlockContainer.SortBlocks() for this. | 
						|
			while (worklist.Count > 0) | 
						|
			{ | 
						|
				int blockIndex = worklist.Min; | 
						|
				worklist.Remove(blockIndex); | 
						|
				Block block = container.Blocks[blockIndex]; | 
						|
				state.ReplaceWith(stateOnBranch[block]); | 
						|
				block.AcceptVisitor(this); | 
						|
			} | 
						|
			if (stateOnLeave.TryGetValue(container, out State stateOnExit)) | 
						|
			{ | 
						|
				state.ReplaceWith(stateOnExit); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				MarkUnreachable(); | 
						|
			} | 
						|
			DebugEndPoint(container); | 
						|
			workLists.Remove(container); | 
						|
		} | 
						|
 | 
						|
		readonly List<(IBranchOrLeaveInstruction, State)> branchesTriggeringFinally = new List<(IBranchOrLeaveInstruction, State)>(); | 
						|
 | 
						|
		protected internal override void VisitBranch(Branch inst) | 
						|
		{ | 
						|
			if (inst.TriggersFinallyBlock) | 
						|
			{ | 
						|
				Debug.Assert(state.LessThanOrEqual(currentStateOnException)); | 
						|
				branchesTriggeringFinally.Add((inst, state.Clone())); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				MergeBranchStateIntoTargetBlock(inst, state); | 
						|
			} | 
						|
			MarkUnreachable(); | 
						|
		} | 
						|
 | 
						|
		void MergeBranchStateIntoTargetBlock(Branch inst, State branchState) | 
						|
		{ | 
						|
			var targetBlock = inst.TargetBlock; | 
						|
			var targetState = GetBlockInputState(targetBlock); | 
						|
			if (!branchState.LessThanOrEqual(targetState)) | 
						|
			{ | 
						|
				targetState.JoinWith(branchState); | 
						|
 | 
						|
				BlockContainer container = (BlockContainer)targetBlock.Parent; | 
						|
				if (workLists.TryGetValue(container, out var workList)) | 
						|
				{ | 
						|
					workList.Add(targetBlock.ChildIndex); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					Debug.Fail("Failed to find target BlockContainer"); | 
						|
				} | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitLeave(Leave inst) | 
						|
		{ | 
						|
			inst.Value.AcceptVisitor(this); | 
						|
			if (inst.TriggersFinallyBlock) | 
						|
			{ | 
						|
				Debug.Assert(state.LessThanOrEqual(currentStateOnException)); | 
						|
				branchesTriggeringFinally.Add((inst, state.Clone())); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				MergeBranchStateIntoStateOnLeave(inst, state); | 
						|
			} | 
						|
			MarkUnreachable(); | 
						|
		} | 
						|
 | 
						|
		void MergeBranchStateIntoStateOnLeave(Leave inst, State branchState) | 
						|
		{ | 
						|
			if (stateOnLeave.TryGetValue(inst.TargetContainer, out State targetState)) | 
						|
			{ | 
						|
				targetState.JoinWith(branchState); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				stateOnLeave.Add(inst.TargetContainer, branchState.Clone()); | 
						|
			} | 
						|
			// Note: We don't have to put the block container onto the work queue, | 
						|
			// because it's an ancestor of the Leave instruction, and hence | 
						|
			// we are currently somewhere within the VisitBlockContainer() call. | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitThrow(Throw inst) | 
						|
		{ | 
						|
			inst.Argument.AcceptVisitor(this); | 
						|
			MarkUnreachable(); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitRethrow(Rethrow inst) | 
						|
		{ | 
						|
			MarkUnreachable(); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitInvalidBranch(InvalidBranch inst) | 
						|
		{ | 
						|
			MarkUnreachable(); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Stores the stateOnException per try instruction. | 
						|
		/// </summary> | 
						|
		readonly Dictionary<TryInstruction, State> stateOnException = new Dictionary<TryInstruction, State>(); | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Visits the TryBlock. | 
						|
		///  | 
						|
		/// Returns a new State object representing the exceptional control flow transfer out of the try block. | 
						|
		/// </summary> | 
						|
		protected State HandleTryBlock(TryInstruction inst) | 
						|
		{ | 
						|
			State oldStateOnException = currentStateOnException; | 
						|
			if (stateOnException.TryGetValue(inst, out State newStateOnException)) | 
						|
			{ | 
						|
				newStateOnException.JoinWith(state); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				newStateOnException = state.Clone(); | 
						|
				stateOnException.Add(inst, newStateOnException); | 
						|
			} | 
						|
 | 
						|
			currentStateOnException = newStateOnException; | 
						|
			inst.TryBlock.AcceptVisitor(this); | 
						|
			// swap back to the old object instance | 
						|
			currentStateOnException = oldStateOnException; | 
						|
 | 
						|
			// No matter what kind of try-instruction this is, it's possible | 
						|
			// that an async exception is thrown immediately in the handler block, | 
						|
			// so propagate the state: | 
						|
			oldStateOnException.JoinWith(newStateOnException); | 
						|
 | 
						|
			// Return a copy, so that the caller mutating the returned state | 
						|
			// does not influence the 'stateOnException' dict | 
						|
			return newStateOnException.Clone(); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitTryCatch(TryCatch inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			State onException = HandleTryBlock(inst); | 
						|
			State endpoint = state.Clone(); | 
						|
			foreach (var handler in inst.Handlers) | 
						|
			{ | 
						|
				state.ReplaceWith(onException); | 
						|
				BeginTryCatchHandler(handler); | 
						|
				handler.Filter.AcceptVisitor(this); | 
						|
				// if the filter return false, any mutations done by the filter | 
						|
				// will be visible by the remaining handlers | 
						|
				// (but it's also possible that the filter didn't get executed at all | 
						|
				// because the exception type doesn't match) | 
						|
				onException.JoinWith(state); | 
						|
 | 
						|
				handler.Body.AcceptVisitor(this); | 
						|
				endpoint.JoinWith(state); | 
						|
			} | 
						|
			state = endpoint; | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected virtual void BeginTryCatchHandler(TryCatchHandler inst) | 
						|
		{ | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// TryCatchHandler is handled directly in VisitTryCatch | 
						|
		/// </summary> | 
						|
		protected internal override sealed void VisitTryCatchHandler(TryCatchHandler inst) | 
						|
		{ | 
						|
			throw new NotSupportedException(); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitTryFinally(TryFinally inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			int branchesTriggeringFinallyOldCount = branchesTriggeringFinally.Count; | 
						|
			// At first, handle 'try { .. } finally { .. }' like 'try { .. } catch {} .. if (?) rethrow; }' | 
						|
			State onException = HandleTryBlock(inst); | 
						|
			State onSuccess = state.Clone(); | 
						|
			state.JoinWith(onException); | 
						|
			inst.FinallyBlock.AcceptVisitor(this); | 
						|
			//PropagateStateOnException(); // rethrow the exception after the finally block -- should be redundant | 
						|
			Debug.Assert(state.LessThanOrEqual(currentStateOnException)); | 
						|
 | 
						|
			ProcessBranchesLeavingTryFinally(inst, branchesTriggeringFinallyOldCount); | 
						|
 | 
						|
			// Use TriggerFinally() to ensure points after the try-finally are reachable only if both the | 
						|
			// try and the finally endpoints are reachable. | 
						|
			onSuccess.TriggerFinally(state); | 
						|
			state = onSuccess; | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Process branches leaving the try-finally, | 
						|
		///  * Calls TriggerFinally() on each branchesTriggeringFinally | 
						|
		///  * Removes entries from branchesTriggeringFinally if they won't trigger additional finally blocks. | 
						|
		///  * After all finallies are applied, the branch state is merged into the target block. | 
						|
		/// </summary> | 
						|
		void ProcessBranchesLeavingTryFinally(TryFinally tryFinally, int branchesTriggeringFinallyOldCount) | 
						|
		{ | 
						|
			int outPos = branchesTriggeringFinallyOldCount; | 
						|
			for (int i = branchesTriggeringFinallyOldCount; i < branchesTriggeringFinally.Count; ++i) | 
						|
			{ | 
						|
				var (branch, stateOnBranch) = branchesTriggeringFinally[i]; | 
						|
				Debug.Assert(((ILInstruction)branch).IsDescendantOf(tryFinally)); | 
						|
				Debug.Assert(tryFinally.IsDescendantOf(branch.TargetContainer)); | 
						|
				stateOnBranch.TriggerFinally(state); | 
						|
				bool triggersAnotherFinally = Branch.GetExecutesFinallyBlock(tryFinally, branch.TargetContainer); | 
						|
				if (triggersAnotherFinally) | 
						|
				{ | 
						|
					branchesTriggeringFinally[outPos++] = (branch, stateOnBranch); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					// Merge state into target block. | 
						|
					if (branch is Leave leave) | 
						|
					{ | 
						|
						MergeBranchStateIntoStateOnLeave((Leave)branch, stateOnBranch); | 
						|
					} | 
						|
					else | 
						|
					{ | 
						|
						MergeBranchStateIntoTargetBlock((Branch)branch, stateOnBranch); | 
						|
					} | 
						|
				} | 
						|
			} | 
						|
			branchesTriggeringFinally.RemoveRange(outPos, branchesTriggeringFinally.Count - outPos); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitTryFault(TryFault inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			// try-fault executes fault block if an exception occurs in try, | 
						|
			// and always rethrows the exception at the end. | 
						|
			State onException = HandleTryBlock(inst); | 
						|
			State onSuccess = state; | 
						|
			state = onException; | 
						|
			inst.FaultBlock.AcceptVisitor(this); | 
						|
			//PropagateStateOnException(); // rethrow the exception after the fault block | 
						|
			Debug.Assert(state.LessThanOrEqual(currentStateOnException)); | 
						|
 | 
						|
			// try-fault exits normally only if no exception occurred | 
						|
			state = onSuccess; | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitIfInstruction(IfInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			var (beforeThen, beforeElse) = EvaluateCondition(inst.Condition); | 
						|
			state = beforeThen; | 
						|
			inst.TrueInst.AcceptVisitor(this); | 
						|
			State afterTrueState = state; | 
						|
			state = beforeElse; | 
						|
			inst.FalseInst.AcceptVisitor(this); | 
						|
			state.JoinWith(afterTrueState); | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Evaluates the condition of an if. | 
						|
		/// </summary> | 
						|
		/// <returns> | 
						|
		/// A pair of: | 
						|
		///  * The state after the condition evaluates to true | 
						|
		///  * The state after the condition evaluates to false | 
						|
		/// </returns> | 
						|
		/// <remarks> | 
						|
		/// <c>this.state</c> is invalid after this function was called, and must be overwritten | 
						|
		/// with one of the return values. | 
						|
		/// </remarks> | 
						|
		(State OnTrue, State OnFalse) EvaluateCondition(ILInstruction inst) | 
						|
		{ | 
						|
			if (inst is IfInstruction ifInst) | 
						|
			{ | 
						|
				// 'if (a?b:c)' or similar. | 
						|
				// This also includes conditions that are logic.not, logic.and, logic.or. | 
						|
				DebugStartPoint(ifInst); | 
						|
				var (beforeThen, beforeElse) = EvaluateCondition(ifInst.Condition); | 
						|
				state = beforeThen; | 
						|
				var (afterThenTrue, afterThenFalse) = EvaluateCondition(ifInst.TrueInst); | 
						|
				state = beforeElse; | 
						|
				var (afterElseTrue, afterElseFalse) = EvaluateCondition(ifInst.FalseInst); | 
						|
 | 
						|
				var onTrue = afterThenTrue; | 
						|
				onTrue.JoinWith(afterElseTrue); | 
						|
				var onFalse = afterThenFalse; | 
						|
				onFalse.JoinWith(afterElseFalse); | 
						|
 | 
						|
				DebugEndPoint(ifInst); | 
						|
				return (onTrue, onFalse); | 
						|
			} | 
						|
			else if (inst is LdcI4 constant) | 
						|
			{ | 
						|
				if (constant.Value == 0) | 
						|
				{ | 
						|
					return (bottomState.Clone(), state); | 
						|
				} | 
						|
				else | 
						|
				{ | 
						|
					return (state, bottomState.Clone()); | 
						|
				} | 
						|
			} | 
						|
			else if (inst is MatchInstruction match) | 
						|
			{ | 
						|
				return EvaluateMatch(match); | 
						|
			} | 
						|
			else | 
						|
			{ | 
						|
				// other kind of condition | 
						|
				inst.AcceptVisitor(this); | 
						|
				return (state, state.Clone()); | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitMatchInstruction(MatchInstruction inst) | 
						|
		{ | 
						|
			var (onTrue, onFalse) = EvaluateMatch(inst); | 
						|
			state = onTrue; | 
						|
			state.JoinWith(onFalse); | 
						|
		} | 
						|
 | 
						|
		/// <summary> | 
						|
		/// Evaluates a match instruction. | 
						|
		/// </summary> | 
						|
		/// <returns> | 
						|
		/// A pair of: | 
						|
		///  * The state after the pattern matches | 
						|
		///  * The state after the pattern fails to match | 
						|
		/// </returns> | 
						|
		/// <remarks> | 
						|
		/// <c>this.state</c> is invalid after this function was called, and must be overwritten | 
						|
		/// with one of the return values. | 
						|
		/// </remarks> | 
						|
		(State OnTrue, State OnFalse) EvaluateMatch(MatchInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.TestedOperand.AcceptVisitor(this); | 
						|
			State onFalse = state.Clone(); | 
						|
			if (!inst.CheckNotNull && !inst.CheckType) | 
						|
			{ | 
						|
				onFalse.ReplaceWithBottom(); | 
						|
			} | 
						|
			HandleMatchStore(inst); | 
						|
			foreach (var subPattern in inst.SubPatterns) | 
						|
			{ | 
						|
				var (subTrue, subFalse) = EvaluateCondition(subPattern); | 
						|
				onFalse.JoinWith(subFalse); | 
						|
				state = subTrue; | 
						|
			} | 
						|
			DebugEndPoint(inst); | 
						|
			return (state, onFalse); | 
						|
		} | 
						|
 | 
						|
		protected abstract void HandleMatchStore(MatchInstruction inst); | 
						|
 | 
						|
		protected internal override void VisitNullCoalescingInstruction(NullCoalescingInstruction inst) | 
						|
		{ | 
						|
			HandleBinaryWithOptionalEvaluation(inst, inst.ValueInst, inst.FallbackInst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitDynamicLogicOperatorInstruction(DynamicLogicOperatorInstruction inst) | 
						|
		{ | 
						|
			HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitUserDefinedLogicOperator(UserDefinedLogicOperator inst) | 
						|
		{ | 
						|
			HandleBinaryWithOptionalEvaluation(inst, inst.Left, inst.Right); | 
						|
		} | 
						|
 | 
						|
		void HandleBinaryWithOptionalEvaluation(ILInstruction parent, ILInstruction left, ILInstruction right) | 
						|
		{ | 
						|
			DebugStartPoint(parent); | 
						|
			left.AcceptVisitor(this); | 
						|
			State branchState = state.Clone(); | 
						|
			right.AcceptVisitor(this); | 
						|
			state.JoinWith(branchState); | 
						|
			DebugEndPoint(parent); | 
						|
		} | 
						|
 | 
						|
		State stateOnNullableRewrap; | 
						|
 | 
						|
		protected internal override void VisitNullableRewrap(NullableRewrap inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			var oldState = stateOnNullableRewrap.Clone(); | 
						|
			stateOnNullableRewrap.ReplaceWithBottom(); | 
						|
 | 
						|
			inst.Argument.AcceptVisitor(this); | 
						|
			// Join incoming control flow from the NullableUnwraps. | 
						|
			state.JoinWith(stateOnNullableRewrap); | 
						|
 | 
						|
			stateOnNullableRewrap = oldState; | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitNullableUnwrap(NullableUnwrap inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.Argument.AcceptVisitor(this); | 
						|
			// Conditional control flow edge to the surrounding NullableRewrap. | 
						|
			stateOnNullableRewrap.JoinWith(state); | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitSwitchInstruction(SwitchInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.Value.AcceptVisitor(this); | 
						|
			State beforeSections = state.Clone(); | 
						|
			inst.Sections[0].AcceptVisitor(this); | 
						|
			State afterSections = state.Clone(); | 
						|
			for (int i = 1; i < inst.Sections.Count; ++i) | 
						|
			{ | 
						|
				state.ReplaceWith(beforeSections); | 
						|
				inst.Sections[i].AcceptVisitor(this); | 
						|
				afterSections.JoinWith(state); | 
						|
			} | 
						|
			state = afterSections; | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitYieldReturn(YieldReturn inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.Value.AcceptVisitor(this); | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitUsingInstruction(UsingInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.ResourceExpression.AcceptVisitor(this); | 
						|
			inst.Body.AcceptVisitor(this); | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitLockInstruction(LockInstruction inst) | 
						|
		{ | 
						|
			DebugStartPoint(inst); | 
						|
			inst.OnExpression.AcceptVisitor(this); | 
						|
			inst.Body.AcceptVisitor(this); | 
						|
			DebugEndPoint(inst); | 
						|
		} | 
						|
 | 
						|
		protected internal override void VisitILFunction(ILFunction function) | 
						|
		{ | 
						|
			throw new NotImplementedException(); | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |