Пример #1
0
        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);
                }
Пример #2
0
        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));
                            }
                        }
                    }
                }
Пример #3
0
        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);
            }
        }