public static void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AwaitInCatchFinally) { return; } HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>(); HashSet <Block> removedBlocks = new HashSet <Block>(); // analyze all try-catch statements in the function foreach (var tryCatch in function.Descendants.OfType <TryCatch>().ToArray()) { if (!(tryCatch.Parent?.Parent is BlockContainer container)) { continue; } // Detect all handlers that contain an await expression AnalyzeHandlers(tryCatch.Handlers, out var catchHandlerIdentifier, out var transformableCatchBlocks); var cfg = new ControlFlowGraph(container, context.CancellationToken); if (transformableCatchBlocks.Count > 0) { changedContainers.Add(container); } SwitchInstruction switchInstructionOpt = null; foreach (var result in transformableCatchBlocks) { removedBlocks.Clear(); var node = cfg.GetNode(result.RealCatchBlockEntryPoint); context.StepStartGroup($"Inline catch block with await (at {result.Handler.Variable.Name})", result.Handler); // Remove the IfInstruction from the jump table and eliminate all branches to the block. switch (result.JumpTableEntry) { case IfInstruction jumpTableEntry: var jumpTableBlock = (Block)jumpTableEntry.Parent; context.Step("Remove jump-table entry", result.JumpTableEntry); jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex); foreach (var branch in tryCatch.Descendants.OfType <Branch>()) { if (branch.TargetBlock == jumpTableBlock) { if (result.NextBlockOrExitContainer is BlockContainer exitContainer) { context.Step("branch jumpTableBlock => leave exitContainer", branch); branch.ReplaceWith(new Leave(exitContainer)); } else { context.Step("branch jumpTableBlock => branch nextBlock", branch); branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer)); } } } break; case SwitchSection jumpTableEntry: Debug.Assert(switchInstructionOpt == null || jumpTableEntry.Parent == switchInstructionOpt); switchInstructionOpt = (SwitchInstruction)jumpTableEntry.Parent; break; } // Add the real catch block entry-point to the block container var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last(); result.RealCatchBlockEntryPoint.Remove(); ((BlockContainer)result.Handler.Body).Blocks.Insert(0, result.RealCatchBlockEntryPoint); // Remove the generated catch block catchBlockHead.Remove(); TransformAsyncThrowToThrow(context, removedBlocks, result.RealCatchBlockEntryPoint); // Inline all blocks that are dominated by the entrypoint of the real catch block foreach (var n in cfg.cfg) { Block block = (Block)n.UserData; if (node.Dominates(n)) { TransformAsyncThrowToThrow(context, removedBlocks, block); if (block.Parent == result.Handler.Body) { continue; } if (!removedBlocks.Contains(block)) { context.Step("Move block", result.Handler.Body); MoveBlock(block, (BlockContainer)result.Handler.Body); } } } // Remove unreachable pattern blocks // TODO : sanity check if (result.NextBlockOrExitContainer is Block nextBlock && nextBlock.IncomingEdgeCount == 0) { List <Block> dependentBlocks = new List <Block>(); Block current = nextBlock; do { foreach (var branch in current.Descendants.OfType <Branch>()) { dependentBlocks.Add(branch.TargetBlock); } current.Remove(); dependentBlocks.Remove(current); current = dependentBlocks.FirstOrDefault(b => b.IncomingEdgeCount == 0); } while (current != null); } // Remove all assignments to the common object variable that stores the exception object. if (result.ObjectVariable != result.Handler.Variable) { foreach (var load in result.ObjectVariable.LoadInstructions.ToArray()) { if (!load.IsDescendantOf(result.Handler)) { continue; } if (load.Parent is CastClass cc && cc.Type.Equals(result.Handler.Variable.Type)) { cc.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(cc).WithILRange(load)); } else { load.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(load)); } } } context.StepEndGroup(keepIfEmpty: true); }
public static void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AwaitInCatchFinally) { return; } HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>(); // analyze all try-catch statements in the function foreach (var tryCatch in function.Descendants.OfType <TryCatch>().ToArray()) { if (!(tryCatch.Parent?.Parent is BlockContainer container)) { continue; } // Detect all handlers that contain an await expression AnalyzeHandlers(tryCatch.Handlers, out var catchHandlerIdentifier, out var transformableCatchBlocks); var cfg = new ControlFlowGraph(container, context.CancellationToken); if (transformableCatchBlocks.Count > 0) { changedContainers.Add(container); } foreach (var result in transformableCatchBlocks) { var node = cfg.GetNode(result.RealCatchBlockEntryPoint); context.Step("Inline catch block with await", result.Handler); // Remove the IfInstruction from the jump table and eliminate all branches to the block. var jumpTableBlock = (Block)result.JumpTableEntry.Parent; jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex); foreach (var branch in tryCatch.Descendants.OfType <Branch>()) { if (branch.TargetBlock == jumpTableBlock) { if (result.NextBlockOrExitContainer is BlockContainer exitContainer) { branch.ReplaceWith(new Leave(exitContainer)); } else { branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer)); } } } // Add the real catch block entry-point to the block container var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last(); result.RealCatchBlockEntryPoint.Remove(); ((BlockContainer)result.Handler.Body).Blocks.Insert(0, result.RealCatchBlockEntryPoint); // Remove the generated catch block catchBlockHead.Remove(); // Inline all blocks that are dominated by the entrypoint of the real catch block foreach (var n in cfg.cfg) { if (((Block)n.UserData).Parent == result.Handler.Body) { continue; } if (node.Dominates(n)) { MoveBlock((Block)n.UserData, (BlockContainer)result.Handler.Body); } } // Remove all assignments to the common object variable that stores the exception object. if (result.ObjectVariableStore != null) { foreach (var load in result.ObjectVariableStore.Variable.LoadInstructions.ToArray()) { if (load.Parent is CastClass cc && cc.Type == result.Handler.Variable.Type) { cc.ReplaceWith(new LdLoc(result.Handler.Variable)); } else { load.ReplaceWith(new LdLoc(result.Handler.Variable)); } } } }
public static void Run(ILFunction function, ILTransformContext context) { if (!context.Settings.AwaitInCatchFinally) { return; } HashSet <BlockContainer> changedContainers = new HashSet <BlockContainer>(); // analyze all try-catch statements in the function foreach (var tryCatch in function.Descendants.OfType <TryCatch>().ToArray()) { if (!(tryCatch.Parent?.Parent is BlockContainer container)) { continue; } // await in finally uses a single catch block with catch-type object if (tryCatch.Handlers.Count != 1 || !(tryCatch.Handlers[0].Body is BlockContainer catchBlockContainer) || !tryCatch.Handlers[0].Variable.Type.IsKnownType(KnownTypeCode.Object)) { continue; } // and consists of an assignment to a temporary that is used outside the catch block // and a jump to the finally block var block = catchBlockContainer.EntryPoint; if (block.Instructions.Count < 2 || !block.Instructions[0].MatchStLoc(out var globalCopyVar, out var value) || !value.MatchLdLoc(tryCatch.Handlers[0].Variable)) { continue; } if (block.Instructions.Count == 3) { if (!block.Instructions[1].MatchStLoc(out var globalCopyVarTemp, out value) || !value.MatchLdLoc(globalCopyVar)) { continue; } globalCopyVar = globalCopyVarTemp; } if (!block.Instructions[block.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally)) { continue; } // globalCopyVar should only be used once, at the end of the finally-block if (globalCopyVar.LoadCount != 1 || globalCopyVar.StoreCount > 2) { continue; } var tempStore = globalCopyVar.LoadInstructions[0].Parent as StLoc; if (tempStore == null || !MatchExceptionCaptureBlock(tempStore, out var exitOfFinally, out var afterFinally, out var blocksToRemove)) { continue; } if (!MatchAfterFinallyBlock(ref afterFinally, blocksToRemove, out bool removeFirstInstructionInAfterFinally)) { continue; } var cfg = new ControlFlowGraph(container, context.CancellationToken); var exitOfFinallyNode = cfg.GetNode(exitOfFinally); var entryPointOfFinallyNode = cfg.GetNode(entryPointOfFinally); var additionalBlocksInFinally = new HashSet <Block>(); var invalidExits = new List <ControlFlowNode>(); TraverseDominatorTree(entryPointOfFinallyNode); void TraverseDominatorTree(ControlFlowNode node) { if (entryPointOfFinallyNode != node) { if (entryPointOfFinallyNode.Dominates(node)) { additionalBlocksInFinally.Add((Block)node.UserData); } else { invalidExits.Add(node); } } if (node == exitOfFinallyNode) { return; } foreach (var child in node.DominatorTreeChildren) { TraverseDominatorTree(child); } } if (invalidExits.Any()) { continue; } context.Step("Inline finally block with await", tryCatch.Handlers[0]); foreach (var blockToRemove in blocksToRemove) { blockToRemove.Remove(); } var finallyContainer = new BlockContainer(); entryPointOfFinally.Remove(); if (removeFirstInstructionInAfterFinally) { afterFinally.Instructions.RemoveAt(0); } changedContainers.Add(container); var outer = BlockContainer.FindClosestContainer(container.Parent); if (outer != null) { changedContainers.Add(outer); } finallyContainer.Blocks.Add(entryPointOfFinally); finallyContainer.AddILRange(entryPointOfFinally); exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3); exitOfFinally.Instructions.Add(new Leave(finallyContainer)); foreach (var branchToFinally in container.Descendants.OfType <Branch>()) { if (branchToFinally.TargetBlock == entryPointOfFinally) { branchToFinally.ReplaceWith(new Branch(afterFinally)); } } foreach (var newBlock in additionalBlocksInFinally) { newBlock.Remove(); finallyContainer.Blocks.Add(newBlock); finallyContainer.AddILRange(newBlock); } tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock)); } // clean up all modified containers foreach (var container in changedContainers) { container.SortBlocks(deleteUnreachableBlocks: true); } }