/// <summary> /// Like PendingState returns the pending state for the given block, but /// it also sets the pending state for this block to null. /// </summary> /// <returns>old pending state</returns> private IDataFlowState PopPendingState (CfgBlock block) { IDataFlowState pending = pendingStates[block.Index]; pendingStates[block.Index] = null; if (Tracing) { Console.WriteLine("PopPendingState: block {0}", (block).UniqueKey); } return pending; }
public IStronglyConnectedComponent SccForBlock (CfgBlock block) { if (this.sccMap == null) { this.sccMap = new Hashtable(); foreach (StronglyConnectedComponent/*<CfgBlock>*/ scc in this.sccs) { foreach (CfgBlock iblock in scc.Nodes) { this.sccMap[iblock] = scc; } } } return (StronglyConnectedComponent) this.sccMap[(block)]; }
protected override IDataFlowState VisitStatement(CfgBlock block, Statement statement, IDataFlowState dfstate) { // For debug purpose try{ Analyzer.WriteLine(new SampleInstructionVisitor().Visit(statement,null)+" ::: " +statement.SourceContext.SourceText); }catch(Exception){ Analyzer.WriteLine("Print error: "+statement); } IDataFlowState result=null; try{ result =(IDataFlowState)(iVisitor.Visit(statement,dfstate)); }catch(ModifiesException e){ typeSystem.HandleError(statement,System.Compiler.Error.Warning,":"+e.Message); }catch(Exception e){ typeSystem.HandleError(statement,System.Compiler.Error.Warning,":CFG1:"+e.Message); } Analyzer.WriteLine(dfstate); return dfstate; }
protected override IDataFlowState VisitBlock(CfgBlock block, IDataFlowState stateOnEntry) { Debug.Assert(block!=null); currentBlock=block; Analyzer.Write("---------block: "+block.UniqueId+";"); Analyzer.Write(" Exit:"); foreach (CfgBlock b in block.NormalSuccessors) Analyzer.Write(b.UniqueId+";"); if (block.UniqueSuccessor!=null) Analyzer.Write(" FallThrough: "+block.UniqueSuccessor+";"); if (block.ExceptionHandler!=null) Analyzer.Write(" ExHandler: "+block.ExceptionHandler.UniqueId+";"); Analyzer.WriteLine(""); NonNullables newState=new NonNullables(stateOnEntry); if (block.ExceptionHandler!=null) this.PushExceptionState(block,newState); return base.VisitBlock (block, newState); }
protected Cci.Block ConvertBlock (CfgBlock block) { return (block); }
/// <summary> /// Add the given state to the pending states of the target block. If /// the block is enabled (by the pending edge count optimization), add the /// block to the worklist. /// /// Inv: DoneState => PendingState /\ PendingState != null => InQueue /// /// Cases: /// 1. Done => new, nothing to do /// 2. Done |_| new is precise. Pend' = Pend |_| new, Done' = Done |_| new /// 3. Done |_| new is imprecise. Pend' = Done |_| new, Done' = Done |_| new /// </summary> public void PushState ( CfgBlock currentBlock, CfgBlock nextBlock, IDataFlowState state ) { if (Tracing) { Console.WriteLine("PushState: block {0} -> {1}", (currentBlock).UniqueKey, (nextBlock).UniqueKey); } // state == null signals that branch is infeasible if (state == null) { return; } bool precise; // Add state to done state IDataFlowState stillPending = this.StateToReanalyzeBlock(currentBlock, nextBlock, state, out precise); if (stillPending == null) { if (Tracing) { Console.WriteLine("PushState: block {0} no new information for pending state.", (nextBlock).UniqueKey); } return; } if (precise) { // join stillPending to old pending. stillPending = this.JoinWithPendingState(currentBlock, nextBlock, stillPending); } this.SetPendingState (nextBlock, stillPending); // when dequeued, the pending state is what the block needs to be analyzed under. // joinWorkItems.Enqueue(nextBlock); if (Tracing) { Console.WriteLine("PushState: block {0} put on work queue.", (nextBlock).UniqueKey); } }
/// <summary> /// It visits an individual statement. It is called from VisitBlock. /// /// It calls NonNullInstructionVisitor /// </summary> /// <param name="block"></param> /// <param name="statement"></param> /// <param name="dfstate"></param> /// <returns></returns> protected override IDataFlowState VisitStatement(CfgBlock block, Statement statement, IDataFlowState dfstate) { // For debug purpose if (Analyzer.Debug) { try{ Analyzer.WriteLine("\n:::"+new SampleInstructionVisitor().Visit(statement,null)+" ::: " +statement.SourceContext.SourceText); }catch(Exception e){ Analyzer.WriteLine("Print error: "+statement+": "+e.Message); } } IDataFlowState result=null; try{ result =(IDataFlowState)(iVisitor.Visit(statement,dfstate)); }catch(Exception e){ typeSystem.HandleError(statement,Cci.Error.InternalCompilerError,":NonNull:"+e.Message); Console.WriteLine(e.StackTrace); } if (result != null && Analyzer.Debug){ result.Dump(); } return result; }
private IDataFlowState PendingState (CfgBlock block) { return(pendingStates[block.Index]); }
public SwitchContinuation(BlockList targets, CfgBlock defaulttarget) { this.targets = targets; this.defaulttarget = defaulttarget; }
private IDataFlowState DoneState (CfgBlock block) { return(doneStates[block.Index]); }
/// <summary> /// Compute the join of two data flow states at the given block. /// </summary> /// <param name="previous">Predecessor block for this new state</param> /// <param name="joinPoint">Block at which join is computed</param> /// <param name="atMerge">Old state at this block. Can be null, in which case the incoming state /// is the first non-bottom state. In this case, the method must set changed /// <c>resultDiffersFromPreviousMerge</c> to true.</param> /// <param name="incoming">New data flow state flowing to this block.</param> /// <param name="resultDiffersFromPreviousMerge">Boolean for fix point. If the state after /// the merge is equal to the old <c>atMerge</c> state, set to false, otherwise set to true.</param> /// <param name="mergeIsPrecise">can be set to true if the merged result state strictly contains only /// information representing either the atMerge or the incoming state, but no extra approximation. If /// this information cannot be determined by the merge, it must return false. True can only be returned /// if result is truly precise.</param> /// <returns>The new merged state.</returns> protected abstract IDataFlowState Merge ( CfgBlock previous, CfgBlock joinPoint, IDataFlowState atMerge, IDataFlowState incoming, out bool resultDiffersFromPreviousMerge, out bool mergeIsPrecise );
/// <summary> /// Invariant: whenever during the traversal, we find that we need to start a new block /// (because we reach a block that could be the target of a branch), /// we have to take the new_stats StatementList and if non-empty, create a new block from it /// and put it onto the new_blocks list. new_stats is then initialized with a new empty list. /// /// We also have to update the orig2newblock map, using the current_oldBlock as the key and the /// newly created block as the target. If we update the map, we null out the current_oldBlock. /// </summary> private void EndStatementList() { if (this.new_stats.Count != 0) { // create block from statements CfgBlock b = new CfgBlock(this.new_stats); // add block to block list this.new_blocks.Add(b); // create new statement list for upcoming statements this.new_stats = new StatementList(); // update map UpdateBlockMap(ref this.current_oldBlock, b); } }
/// <summary> /// Can be called to recursively flatten a block from the following places: /// 1. From within a block on a nested block /// 2. From within a BlockExpression /// 3. From the top-level method body block /// /// Because of 2 and 3, we may have a pending list of statements in new_stats that belong /// to the previous block. Thus we first need to end that block, then start the new one. /// /// Furthermore, since the old block could be a branch target, we also must update the /// orig2newBlock map once we generated the first block within this flattening. /// /// Every block expansion starts with a call to FlattenBlock /// The FlattenBlock stack activation frame keeps track of the prior current_oldBlock /// and stores the new current oldBlock. /// At the end of FlattenBlock, the prior_oldBlock is mapped to the same new block /// as the oldBlock on which FlattenBlock was called. /// /// POST: this.current_oldBlock == null. /// </summary> /// <param name="block"></param> private void FlattenBlock (Block block) { // This definitely starts a new block. If the statement list contains any statements, // finish that block. EndStatementList(); // stack the prior oldBlock (this must be done AFTER we call EndStatementList !) Block prior_oldBlock = this.current_oldBlock; // set the block we are processing as the current old block this.current_oldBlock = block; StatementList sl = block.Statements; // we deal with an empty block on exit of this method. try { if (sl != null) { for (int i = 0; i<sl.Count; i++) { Statement stmt = sl[i]; if (stmt == null) continue; Block nested = stmt as Block; if (nested != null) { FlattenBlock(nested); } else { // Statement to be added to current statement sequence. Statement newstat = (Statement)this.Visit(stmt); if (newstat != null) { // null indicates statement was omitted (e.g. branch false) if (newstat.SourceContext.Document == null){ // MB: Guarantee that every statement coming out of flattener has some kind of source context // REVIEW: It is possible that if newstat happens to be shared (i.e., *not* a copy) // then this will cause more sequence points to be defined in the PDB file than otherwise // which could degrade the debugging experience. newstat.SourceContext = this.current_source_context; } this.new_stats.Add(newstat); if (StatementEndsBlock(newstat)) { EndStatementList(); } } } } // end for } // end if sl != null } finally { // we have to do 2 things here: // // 1. end the block (there could be outstanding statements that need to be emitted) // 2. if we still have this.current_oldBlock != null, then the block contained no statements and we have to insert an empty block // 3. if prior_oldBlock is not null, we have to map it to the same new block as the block we processed in this call. // // The order of these operations is important! Because we side effect this.current_oldBlock and this.orig2newBlock map, these // steps CANNOT be reordered. // Do 1. EndStatementList(); // Do 2. if (this.current_oldBlock != null) { // Debug.Assert (sl == null || sl.Length == 0); // too strict. Fires if sl contains null statements. // create empty block CfgBlock newBlock = new CfgBlock(new StatementList(0)); // add to block list this.new_blocks.Add(newBlock); // update map this.UpdateBlockMap(ref this.current_oldBlock, newBlock); } // Do 3. if (prior_oldBlock != null) { CfgBlock newBlock = (CfgBlock) this.orig2newBlocks[block.UniqueKey]; this.orig2newBlocks[prior_oldBlock.UniqueKey] = newBlock; } } }
public StraightContinuation (CfgBlock target) { this.target = target; }
internal StackElem(CfgBlock node) { this.node = node; this.nextChild = 0; }
/// <summary> /// CFG pretty-printer. For each block, this method /// calls <c>pre_printer</c>c(if non-null), /// prints the normal/excp. successors, /// the code of the block, the normal/excp. predecessors and finally calls /// <c>post_printer</c>. A useful use of the pre and post printers is the /// debugging of a flow-sensitive analysis. /// </summary> /// <param name="tw">Where to print.</param> public void Display( TextWriter tw, CfgBlock[] blocks, DGetBlockInfo get_pre_block_info, DGetBlockInfo get_post_block_info, DGetStatInfo get_stat_info ) { Hashtable b2id = new Hashtable(); for(int i = 0; i < blocks.Length; i++) b2id[blocks[i]] = i; for(int i = 0; i < blocks.Length; i++) { CfgBlock block = blocks[i]; tw.WriteLine("BLOCK " + (block is ISpecialBlock ? "*" : "") + CodePrinter.b2s(block, b2id)); if (get_pre_block_info != null) tw.WriteLine(get_pre_block_info(block)); print_block_array(" Normal pred: ", NormalPred(block), b2id, tw); print_block_array(" Protected blocks: ", ExcpPred(block), b2id, tw); if (ExcpPred(block).Length != 0) { ExceptionHandler eh = HandlerThatStartsAtBlock(block); if (eh != null) tw.WriteLine(" Handler {0} [{1},{2})", eh.HandlerType, CodePrinter.b2s(eh.HandlerStartBlock, b2id), CodePrinter.b2s(eh.BlockAfterHandlerEnd, b2id)); } CodePrinter.PrintBlock(tw, block, get_stat_info, b2id); print_block_array(" Normal succ: ", NormalSucc(block), b2id, tw); print_block_array(" Excp succ: ", this.ExcpSucc(block), b2id, tw); print_block(" Handler: ", ExceptionHandler(block), b2id, tw); print_block_list(" Finallies: ", (FList)b2_enclosing_finally[block], b2id, tw); ExceptionHandler ceh = (ExceptionHandler)b2_containing_handler[block]; if (ceh != null) print_block(" Containing handler", ceh.HandlerStartBlock, b2id, tw); if (get_post_block_info != null) tw.WriteLine(get_post_block_info(block)); tw.WriteLine(); } tw.WriteLine("Entry = " + CodePrinter.b2s(Entry, b2id)); tw.WriteLine("NormalExit = " + CodePrinter.b2s(NormalExit, b2id)); tw.WriteLine("ExcptExit = " + CodePrinter.b2s(ExceptionExit, b2id)); tw.WriteLine("Exit = " + CodePrinter.b2s(Exit, b2id)); }
/// <summary> /// Default per block visitor. Called from Run. /// /// It calls VisitStatement on each statement in a block. /// /// The result of this method is used as the state for all normal control flow successors. /// To push state onto an exception handler, use the PushExceptionState method. Furthermore, for /// conditional branches, different states can be pushed onto the true and false branches directly /// by calling PushPending. In that case, null should be returned from the method in order to avoid pushing /// the returned state onto both true and false targets again. /// </summary> protected virtual IDataFlowState VisitBlock (CfgBlock block, IDataFlowState stateOnEntry) { IDataFlowState currentState = stateOnEntry; for (int i=0; i<block.Length; i++) { if (currentState == null) return null; currentState = this.VisitStatement(block, (Statement)block[i], currentState); } return currentState; }
/// <summary> /// Merge the new pending state with the old pending states. /// </summary> /// <returns>merged pending state</returns> private IDataFlowState JoinWithPendingState (CfgBlock prev, CfgBlock block, IDataFlowState newState) { if (Tracing) { Console.WriteLine("JoinWithPendingState: block {0} -> {1}", (prev).UniqueKey, (block).UniqueKey); } IDataFlowState pending = PendingState(block); // note, we call Merge even if old is null. bool changed; bool precise; if (Tracing) { SourceContext sc = (block).SourceContext; if (sc.Document == null && block.Length > 0) { sc = ((Statement)block[0]).SourceContext; } Console.WriteLine("Join with pending state at line {0}", sc.StartLine); } IDataFlowState merged = this.Merge(prev, block, pending, newState, out changed, out precise); if (Tracing) { Console.WriteLine("Join changed {0}", changed); } return merged; }
/// <summary> /// Default per statement visitor called from the default VisitBlock. /// Does identity transformation. Subclasses either override this method /// if the default block handling is sufficient, or they override the Visit method for blocks. /// /// The result of this method is used as the state for all normal control flow successors. /// To push state onto an exception handler, use the PushExceptionState method. Furthermore, for /// conditional branches, different states can be pushed onto the true and false branches directly /// by calling PushPending. In that case, null should be returned from the method in order to avoid pushing /// the returned state onto both true and false targets again. /// </summary> protected virtual IDataFlowState VisitStatement (CfgBlock block, Statement statement, IDataFlowState dfstate) { // simple case analysis to distinguish throws switch (statement.NodeType) { case NodeType.Throw: case NodeType.Rethrow: { PushExceptionState(block, dfstate); return null; } default: return dfstate; } }
private void SetPendingState (CfgBlock block, IDataFlowState pending) { pendingStates[block.Index] = pending; if (Tracing) { Console.WriteLine("SetPendingState: block {0}", (block).UniqueKey); } }
/// <summary> /// Splits the exceptions into the ones that this handler will handle and the ones that should /// <code>currentHandlerState</code> and <code>nextHandlerState</code> cannot both be null. /// On exit, if <code>currentHandlerState</code> is null, <code>handler</code> handles no exceptions, /// and if <code>nextHandlerState</code> is null, <code>handler</code> handles all the exceptions in /// the initial exception set of <code>currentHandlerState</code>. /// </summary> // go on to the next handler. currentHandlerState and next protected abstract void SplitExceptions ( CfgBlock handler, ref IDataFlowState currentHandlerState, out IDataFlowState nextHandlerState );
/// <summary> /// Merge the two states for current block. /// </summary> /// <param name="previous"></param> /// <param name="joinPoint"></param> /// <param name="atMerge"></param> /// <param name="incoming"></param> /// <param name="resultDiffersFromPreviousMerge"></param> /// <param name="mergeIsPrecise"></param> /// <returns></returns> protected override IDataFlowState Merge(CfgBlock previous, CfgBlock joinPoint, IDataFlowState atMerge, IDataFlowState incoming, out bool resultDiffersFromPreviousMerge, out bool mergeIsPrecise) { mergeIsPrecise = false; // No new states; if(incoming==null) { resultDiffersFromPreviousMerge = false; return atMerge; } // Initialize states if(atMerge==null){ resultDiffersFromPreviousMerge = true; return incoming; } if (Analyzer.Debug) { Console.WriteLine("Merge at Block {0}-----------------", (joinPoint).UniqueKey); Console.WriteLine(" State at merge"); atMerge.Dump(); Console.WriteLine(" Incoming"); incoming.Dump(); } // Merge the two. ExposureState newState = ExposureState.Join((ExposureState)atMerge, (ExposureState)incoming, joinPoint); if (newState != null) { if (Analyzer.Debug) { Console.WriteLine("\n Merged State"); newState.Dump(); } resultDiffersFromPreviousMerge = true; return newState; } if (Analyzer.Debug) { Console.WriteLine("Merged state same as old."); } resultDiffersFromPreviousMerge = false; return atMerge; }
/// <summary> /// Checks if a block needs to be reanalyzed and under what state. /// /// Updates the doneState of this block to reflect the pending state /// </summary> /// <returns>null if no reanalysis necessary, the DFS state if the merge is precise, /// the merged state if the merge is imprecise /// </returns> private IDataFlowState StateToReanalyzeBlock (CfgBlock prev, CfgBlock block, IDataFlowState pending, out bool preciseMerge) { IDataFlowState done = DoneState(block); // note, we call Merge even if old is null. bool changed; if (Tracing) { Console.WriteLine("StateToReanalyzeBlock: block {0} -> {1}", (prev).UniqueKey, (block).UniqueKey); SourceContext sc = (block).SourceContext; if (sc.Document == null && block.Length > 0) { sc = ((Statement)block[0]).SourceContext; } Console.WriteLine("StateToReanalyzeBlock at line {0}", sc.StartLine); } IDataFlowState merged = this.Merge(prev, block, done, pending, out changed, out preciseMerge); if (merged != null) doneStates[block.Index] = merged; if ( ! changed ) { if (Tracing) { Console.WriteLine("Done State"); done.Dump(); Console.WriteLine("Pending State"); pending.Dump(); Console.WriteLine("StateToReanalyzeBlock result UNchanged"); } return null; } if ( preciseMerge ) return pending; if (Tracing) { Console.WriteLine("StateToReanalyzeBlock result CHANGED"); if (done == null) { Console.WriteLine("no done state yet"); } else { Console.WriteLine("Done State"); done.Dump(); } Console.WriteLine("Pending State"); pending.Dump(); Console.WriteLine("Merged State"); merged.Dump(); } return merged; }
public IfContinuation(CfgBlock truetarget, CfgBlock falsetarget) { this.truetarget = truetarget; this.falsetarget = falsetarget; }
/// <summary> /// Returns null, if result of Join is the same as atMerge. /// </summary> public static ExposureState Join(ExposureState atMerge, ExposureState incoming, CfgBlock joinPoint) { bool unchanged; IEGraph merged = atMerge.egraph.Join(incoming.egraph, joinPoint, out unchanged); TypeNode currentException = (atMerge.currentException != null)? ((incoming.currentException != null)? CciHelper.LeastCommonAncestor(atMerge.currentException, incoming.currentException) : null) : null; if (atMerge.currentException != currentException || !unchanged) { return new ExposureState(merged, currentException, atMerge.typeSystem); } return null; }
public void Enqueue(CfgBlock b) { this.Add(b); }
/// <summary> /// Implementation of visit Block. It is called from run. /// /// It calls VisitStatement. /// </summary> /// <param name="block"></param> /// <param name="stateOnEntry"></param> /// <returns></returns> protected override IDataFlowState VisitBlock(CfgBlock block, IDataFlowState stateOnEntry) { Debug.Assert(block!=null); currBlock=block; Analyzer.Write("---------block: "+block.UniqueId+";"); Analyzer.Write(" Exit:"); foreach (CfgBlock b in block.NormalSuccessors) Analyzer.Write(b.UniqueId+";"); if (block.UniqueSuccessor!=null) Analyzer.Write(" FallThrough: "+block.UniqueSuccessor+";"); if (block.ExceptionHandler!=null) Analyzer.Write(" ExHandler: "+block.ExceptionHandler.UniqueId+";"); Analyzer.WriteLine(""); ExposureState newState; if(stateOnEntry==null) newState=new ExposureState(typeSystem); else newState=new ExposureState((ExposureState)stateOnEntry); // if (block.ExceptionHandler!=null) // this.PushExceptionState(block,newState); return base.VisitBlock (block, newState); }
/// <summary> /// Starts the analysis at the first instruction of the given block /// </summary> protected virtual void Run (ControlFlowGraph cfg, CfgBlock startBlock, IDataFlowState startState) { this.Reinitialize(cfg); pendingStates[startBlock.Index] = startState; joinWorkItems.Enqueue(startBlock); while (joinWorkItems.Count > 0) { //joinWorkItems.Dump(); CfgBlock currentBlock = joinWorkItems.Dequeue(); if (Analyzer.Debug) { Console.WriteLine("\n*** Working on block {0} [{1} statements, line {2}]\n", ((currentBlock).UniqueKey), currentBlock.Length, (currentBlock.Length == 0)? -1 : ((Statement)currentBlock[0]).SourceContext.StartLine); } // Flow the current state through the block. IDataFlowState currentState = PopPendingState(currentBlock); currentState = VisitBlock(currentBlock, currentState); // NOTE: VisitBlock may have explicitly pushed states onto some successors. In that case // it should return null to avoid this code pushing the same state onto all successors. if (currentState != null) { foreach (CfgBlock succ in currentBlock.NormalSuccessors) { PushState(currentBlock, succ, currentState); } } } //while }
/// <summary> /// It split exceptions for current handler and the next chained handler. /// /// It will: /// /// If the exception is completely intercepted by current handler, the /// exception will be consumed. /// /// If the exception caught but not completely, both current handler and /// the next handler will take the states. /// /// If the exception is irrelevant to current caught, the next handler /// will take over the state. Current handler is then bypassed. /// </summary> /// <param name="handler"></param> /// <param name="currentHandlerState"></param> /// <param name="nextHandlerState"></param> protected override void SplitExceptions(CfgBlock handler, ref IDataFlowState currentHandlerState, out IDataFlowState nextHandlerState) { Debug.Assert(currentHandlerState!=null,"Internal error in NonNull Analysis"); ExposureState state = (ExposureState)currentHandlerState; if(handler==null || handler.Length==0){ nextHandlerState=null; return; } if(handler[0] is Unwind){ nextHandlerState=null; currentHandlerState=null; return; } Debug.Assert(handler[0] is Catch, "Exception Handler does not starts with Catch"); Debug.Assert(state.currentException!=null,"No current exception to handle"); Catch c=(Catch)handler[0]; if(handler.ExceptionHandler!=null && !state.currentException.IsAssignableTo(c.Type)) { nextHandlerState = state;; } else { nextHandlerState=null; } // Compute what trickles through to the next handler // and what sticks to this handler. if(state.currentException.IsAssignableTo(c.Type)) { // everything sticks nextHandlerState = null; } else if (c.Type.IsAssignableTo(state.currentException)) { // c sticks, rest goes to next handler nextHandlerState = state; // copy state to modify the currentException state = new ExposureState(state); state.currentException = c.Type; currentHandlerState = state; }else { // nothing stick all goes to next handler nextHandlerState = state; currentHandlerState = null; } return; }
/// <summary> /// Push the given state onto the handler of the block. /// This causes call-backs to SplitException in order to correctly distribute /// the exception state among different nested handlers. /// </summary> /// <param name="currentBlock">Block from which exception escapes</param> /// <param name="state">state on exception flow</param> public void PushExceptionState ( CfgBlock currentBlock, IDataFlowState state ) { IDataFlowState currentHandlerState = state; CfgBlock currentHandler = currentBlock; IDataFlowState nextHandlerState; while (currentHandlerState != null) { Debug.Assert(currentHandler.ExceptionHandler != null, String.Format("block {0} does not have an exception handler", (currentHandler).UniqueKey)); currentHandler = currentHandler.ExceptionHandler; if (Tracing) { Console.WriteLine("PushExceptionState (in loop): block {0} -> {1}", (currentBlock).UniqueKey, (currentHandler).UniqueKey); } SplitExceptions(currentHandler, ref currentHandlerState, out nextHandlerState); /// We allow SplitExceptions to make decisions about not propagating any exceptions /// Debug.Assert(currentHandlerState != null || nextHandlerState != null); if (currentHandlerState != null) { PushState(currentBlock, currentHandler, currentHandlerState); } currentHandlerState = nextHandlerState; } }