All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.strobel.decompiler.ast.GotoRemoval Maven / Gradle / Ivy

There is a newer version: 2.5.0.Final
Show newest version
/*
 * GotoRemoval.java
 *
 * Copyright (c) 2013 Mike Strobel
 *
 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
 * A copy of the license can be found in the License.html file at the root of this distribution.
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
 * Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 */

package com.strobel.decompiler.ast;

import com.strobel.annotations.NotNull;
import com.strobel.core.CollectionUtilities;
import com.strobel.core.StrongBox;
import com.strobel.core.VerifyArgument;
import com.strobel.util.ContractUtils;

import java.util.*;

import static com.strobel.assembler.metadata.Flags.testAny;
import static com.strobel.core.CollectionUtilities.*;
import static com.strobel.decompiler.ast.PatternMatching.*;

@SuppressWarnings("ConstantConditions")
final class GotoRemoval {
    final static int OPTION_MERGE_ADJACENT_LABELS = 0x01;
    final static int OPTION_REMOVE_REDUNDANT_RETURNS = 0x02;

    final Map labels = new IdentityHashMap<>();
    final Map labelLookup = new IdentityHashMap<>();
    final Map parentLookup = new IdentityHashMap<>();
    final Map nextSibling = new IdentityHashMap<>();

    final int options;

    GotoRemoval() {
        this(0);
    }

    GotoRemoval(final int options) {
        this.options = options;
    }

    public final void removeGotos(final Block method) {
        traverseGraph(method);
        removeGotosCore(method);
    }

    private void removeGotosCore(final Block method) {
        transformLeaveStatements(method);

        boolean modified;

        do {
            modified = false;

            for (final Expression e : method.getSelfAndChildrenRecursive(Expression.class)) {
                if (e.getCode() == AstCode.Goto) {
                    modified |= trySimplifyGoto(e);
                }
            }
        }
        while (modified);

        removeRedundantCodeCore(method);
    }

    private void traverseGraph(final Block method) {
        labels.clear();
        labelLookup.clear();
        parentLookup.clear();
        nextSibling.clear();

        parentLookup.put(method, Node.NULL);

        for (final Node node : method.getSelfAndChildrenRecursive(Node.class)) {
            Node previousChild = null;

            for (final Node child : node.getChildren()) {
                if (parentLookup.containsKey(child)) {
                    throw Error.expressionLinkedFromMultipleLocations(child);
                }

                parentLookup.put(child, node);

                if (previousChild != null) {
                    if (previousChild instanceof Label) {
                        labels.put(child, (Label) previousChild);
                        labelLookup.put((Label) previousChild, child);
                    }
                    nextSibling.put(previousChild, child);
                }

                previousChild = child;
            }

            if (previousChild != null) {
                nextSibling.put(previousChild, Node.NULL);
            }
        }
    }

    private boolean trySimplifyGoto(final Expression gotoExpression) {
        assert gotoExpression.getCode() == AstCode.Goto;
        assert gotoExpression.getOperand() instanceof Label;

        final Node target = enter(gotoExpression, new LinkedHashSet());

        if (target == null) {
            return false;
        }

        //
        // The goto expression is marked as visited because we do not want to iterate over
        // nodes which we plan to modify.
        //
        // The simulated path always has to start in the same try block in order for the
        // same finally blocks to be executed.
        //

        final Set visitedNodes = new LinkedHashSet<>();

        visitedNodes.add(gotoExpression);

        final Node exitTo = exit(gotoExpression, visitedNodes);
        final boolean isRedundant = target == exitTo;

        if (isRedundant) {
            final Node parent = parentLookup.get(gotoExpression);

            //
            // For now, only remove redundant goto expressions that are unlikely to be of the form
            // `if (x) continue`.  This is an aesthetic choice.
            //

            if (!(parent instanceof Block &&
                  ((Block) parent).getBody().size() == 1 &&
                  parentLookup.get(parent) instanceof Condition)) {

                gotoExpression.setCode(AstCode.Nop);
                gotoExpression.setOperand(null);

                if (target instanceof Expression) {
                    ((Expression) target).getRanges().addAll(gotoExpression.getRanges());
                }

                gotoExpression.getRanges().clear();
                return true;
            }
        }

        visitedNodes.clear();
        visitedNodes.add(gotoExpression);

        for (final TryCatchBlock tryCatchBlock : getParents(gotoExpression, TryCatchBlock.class)) {
            final Block finallyBlock = tryCatchBlock.getFinallyBlock();

            if (finallyBlock == null) {
                continue;
            }

            if (target == enter(finallyBlock, visitedNodes)) {
                gotoExpression.setCode(AstCode.Nop);
                gotoExpression.setOperand(null);
                gotoExpression.getRanges().clear();
                return true;
            }
        }

        visitedNodes.clear();
        visitedNodes.add(gotoExpression);

        //
        // Look for single-level `continue` statements first.
        //

        Loop continueBlock = null;

        for (final Node parent : getParents(gotoExpression)) {
            if (parent instanceof Loop) {
                final Node enter = enter(parent, visitedNodes);

                if (target == enter) {
                    continueBlock = (Loop) parent;
                    break;
                }

                if (enter instanceof TryCatchBlock) {
                    final Node firstChild = firstOrDefault(enter.getChildren());

                    if (firstChild != null) {
                        visitedNodes.clear();
                        if (enter(firstChild, visitedNodes) == target) {
                            continueBlock = (Loop) parent;
                            break;
                        }
                    }
                }

                break;
            }
        }

        if (continueBlock != null) {
            gotoExpression.setCode(AstCode.LoopContinue);
            gotoExpression.setOperand(null);
            return true;
        }

        //
        // Remove redundant goto statements that are NOT conditional continue statements.
        //

        if (isRedundant) {
            gotoExpression.setCode(AstCode.Nop);
            gotoExpression.setOperand(null);

            if (target instanceof Expression) {
                ((Expression) target).getRanges().addAll(gotoExpression.getRanges());
            }

            gotoExpression.getRanges().clear();
            return true;
        }

        visitedNodes.clear();
        visitedNodes.add(gotoExpression);

        //
        // Now look for loop/switch break statements.
        //

        int loopDepth = 0;
        int switchDepth = 0;
        Node breakBlock = null;

        for (final Node parent : getParents(gotoExpression)) {
            if (parent instanceof Loop) {
                ++loopDepth;

                final Node exit = exit(parent, visitedNodes);

                if (target == exit) {
                    breakBlock = parent;
                    break;
                }

                if (exit instanceof TryCatchBlock) {
                    final Node firstChild = firstOrDefault(exit.getChildren());

                    if (firstChild != null) {
                        visitedNodes.clear();
                        if (enter(firstChild, visitedNodes) == target) {
                            breakBlock = parent;
                            break;
                        }
                    }
                }
            }
            else if (parent instanceof Switch) {
                ++switchDepth;

                final Node exit = exit(parent, visitedNodes);

                if (target == exit) {
                    breakBlock = parent;
                    break;
                }
            }
        }

        if (breakBlock != null) {
            gotoExpression.setCode(AstCode.LoopOrSwitchBreak);
            gotoExpression.setOperand((loopDepth + switchDepth) > 1 ? gotoExpression.getOperand() : null);
            return true;
        }

        visitedNodes.clear();
        visitedNodes.add(gotoExpression);

        //
        // Now look for outer loop continue statements.
        //

        loopDepth = 0;

        for (final Node parent : getParents(gotoExpression)) {
            if (parent instanceof Loop) {
                ++loopDepth;

                final Node enter = enter(parent, visitedNodes);

                if (target == enter) {
                    continueBlock = (Loop) parent;
                    break;
                }

                if (enter instanceof TryCatchBlock) {
                    final Node firstChild = firstOrDefault(enter.getChildren());

                    if (firstChild != null) {
                        visitedNodes.clear();
                        if (enter(firstChild, visitedNodes) == target) {
                            continueBlock = (Loop) parent;
                            break;
                        }
                    }
                }
            }
        }

        if (continueBlock != null) {
            gotoExpression.setCode(AstCode.LoopContinue);
            gotoExpression.setOperand(loopDepth > 1 ? gotoExpression.getOperand() : null);
            return true;
        }

        //
        // Lastly, try to duplicate return/throw statements at the target site.
        //

        return tryInlineReturn(gotoExpression, target, AstCode.Return) ||
               tryInlineReturn(gotoExpression, target, AstCode.AThrow);
    }

    private boolean tryInlineReturn(final Expression gotoExpression, final Node target, final AstCode code) {
        final List expressions = new ArrayList<>();

        if (matchGetArguments(target, code, expressions) &&
            (expressions.isEmpty() ||
             expressions.size() == 1/* && Inlining.hasNoSideEffect(expressions.get(0))*/)) {

            gotoExpression.setCode(code);
            gotoExpression.setOperand(null);
            gotoExpression.getArguments().clear();

            if (!expressions.isEmpty()) {
                gotoExpression.getArguments().add(expressions.get(0).clone());
            }

            return true;
        }

        final StrongBox v = new StrongBox<>();
        final StrongBox v2 = new StrongBox<>();

        Node next = nextSibling.get(target);

        while (next instanceof Label) {
            next = nextSibling.get(next);
        }

        if (matchGetArguments(target, AstCode.Store, v, expressions) &&
            expressions.size() == 1 &&
            /*Inlining.hasNoSideEffect(expressions.get(0)) &&*/
            matchGetArguments(next, code, expressions) &&
            expressions.size() == 1 &&
            matchGetOperand(expressions.get(0), AstCode.Load, v2) &&
            v2.get() == v.get()) {

            gotoExpression.setCode(code);
            gotoExpression.setOperand(null);
            gotoExpression.getArguments().clear();
            gotoExpression.getArguments().add(((Expression) target).getArguments().get(0).clone());

            return true;
        }

        return false;
    }

    private Iterable getParents(final Node node) {
        return getParents(node, Node.class);
    }

    private  Iterable getParents(final Node node, final Class parentType) {
        return new Iterable() {
            @NotNull
            @Override
            public final Iterator iterator() {
                return new Iterator() {
                    T current = updateCurrent(node);

                    @SuppressWarnings("unchecked")
                    private T updateCurrent(Node node) {
                        while (node != null && node != Node.NULL) {
                            node = parentLookup.get(node);

                            if (parentType.isInstance(node)) {
                                return (T) node;
                            }
                        }

                        return null;
                    }

                    @Override
                    public final boolean hasNext() {
                        return current != null;
                    }

                    @Override
                    public final T next() {
                        final T next = current;

                        if (next == null) {
                            throw new NoSuchElementException();
                        }

                        current = updateCurrent(next);
                        return next;
                    }

                    @Override
                    public final void remove() {
                        throw ContractUtils.unsupported();
                    }
                };
            }
        };
    }

    private Node enter(final Node node, final Set visitedNodes) {
        VerifyArgument.notNull(node, "node");
        VerifyArgument.notNull(visitedNodes, "visitedNodes");

        if (!visitedNodes.add(node)) {
            //
            // Infinite loop.
            //
            return null;
        }

        if (node instanceof Label) {
            return exit(node, visitedNodes);
        }

        if (node instanceof Expression) {
            final Expression e = (Expression) node;

            switch (e.getCode()) {
                case Goto: {
                    final Label target = (Label) e.getOperand();

                    //
                    // Early exit -- same try block.
                    //
                    if (firstOrDefault(getParents(e, TryCatchBlock.class)) ==
                        firstOrDefault(getParents(target, TryCatchBlock.class))) {

                        return enter(target, visitedNodes);
                    }

                    //
                    // Make sure we are not entering a try block.
                    //
                    final List sourceTryBlocks = toList(getParents(e, TryCatchBlock.class));
                    final List targetTryBlocks = toList(getParents(target, TryCatchBlock.class));

                    Collections.reverse(sourceTryBlocks);
                    Collections.reverse(targetTryBlocks);

                    //
                    // Skip blocks we are already in.
                    //
                    int i = 0;

                    while (i < sourceTryBlocks.size() &&
                           i < targetTryBlocks.size() &&
                           sourceTryBlocks.get(i) == targetTryBlocks.get(i)) {
                        i++;
                    }

                    if (i == targetTryBlocks.size()) {
                        return enter(target, visitedNodes);
                    }

                    final TryCatchBlock targetTryBlock = targetTryBlocks.get(i);

                    //
                    // Check that the goto points to the start.
                    //
                    TryCatchBlock current = targetTryBlock;

                    while (current != null) {
                        final List body = current.getTryBlock().getBody();

                        current = null;

                        for (final Node n : body) {
                            if (n instanceof Label) {
                                if (n == target) {
                                    return targetTryBlock;
                                }
                            }
                            else if (!match(n, AstCode.Nop)) {
                                current = n instanceof TryCatchBlock ? (TryCatchBlock) n : null;
                                break;
                            }
                        }
                    }

                    return null;
                }

                default: {
                    return e;
                }
            }
        }

        if (node instanceof Block) {
            final Block block = (Block) node;

            if (block.getEntryGoto() != null) {
                return enter(block.getEntryGoto(), visitedNodes);
            }

            if (block.getBody().isEmpty()) {
                return exit(block, visitedNodes);
            }

            return enter(block.getBody().get(0), visitedNodes);
        }

        if (node instanceof Condition) {
            return ((Condition) node).getCondition();
        }

        if (node instanceof Loop) {
            final Loop loop = (Loop) node;

            if (loop.getLoopType() == LoopType.PreCondition && loop.getCondition() != null) {
                return loop.getCondition();
            }

            return enter(loop.getBody(), visitedNodes);
        }

        if (node instanceof TryCatchBlock) {
            return node;
        }

        if (node instanceof Switch) {
            return ((Switch) node).getCondition();
        }

        throw Error.unsupportedNode(node);
    }

    private Node exit(final Node node, final Set visitedNodes) {
        VerifyArgument.notNull(node, "node");
        VerifyArgument.notNull(visitedNodes, "visitedNodes");

        final Node parent = parentLookup.get(node);

        if (parent == null || parent == Node.NULL) {
            //
            // Exited main body.
            //
            return null;
        }

        if (parent instanceof Block) {
            final Node nextNode = nextSibling.get(node);

            if (nextNode != null && nextNode != Node.NULL) {
                return enter(nextNode, visitedNodes);
            }

            if (parent instanceof CaseBlock) {
                final Node nextCase = nextSibling.get(parent);

                if (nextCase != null && nextCase != Node.NULL) {
                    return enter(nextCase, visitedNodes);
                }
            }

            return exit(parent, visitedNodes);
        }

        if (parent instanceof Condition) {
            return exit(parent, visitedNodes);
        }

        if (parent instanceof TryCatchBlock) {
            //
            // Finally blocks are completely ignored.  We rely on the fact that try blocks
            // cannot be entered.
            //
            return exit(parent, visitedNodes);
        }

        if (parent instanceof Switch) {
            //
            // Implicit exit from switch is not allowed.
            //
            return null;
        }

        if (parent instanceof Loop) {
            return enter(parent, visitedNodes);
        }

        throw Error.unsupportedNode(parent);
    }

    @SuppressWarnings("ConstantConditions")
    private void transformLeaveStatements(final Block method) {
        final StrongBox




© 2015 - 2024 Weber Informatics LLC | Privacy Policy