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

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

There is a newer version: 2.5.0.Final
Show newest version
/*
 * AstOptimizer.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.assembler.metadata.*;
import com.strobel.core.*;
import com.strobel.decompiler.DecompilerContext;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.functions.Function;
import com.strobel.functions.Supplier;
import com.strobel.functions.Suppliers;
import com.strobel.util.ContractUtils;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.strobel.core.CollectionUtilities.*;
import static com.strobel.decompiler.ast.PatternMatching.*;

@SuppressWarnings("ConstantConditions")
public final class AstOptimizer {
    private final static Logger LOG = Logger.getLogger(AstOptimizer.class.getSimpleName());

    private int _nextLabelIndex;

    public static void optimize(final DecompilerContext context, final Block method) {
        optimize(context, method, AstOptimizationStep.None);
    }

    public static void optimize(final DecompilerContext context, final Block method, final AstOptimizationStep abortBeforeStep) {
        VerifyArgument.notNull(context, "context");
        VerifyArgument.notNull(method, "method");

        LOG.fine("Beginning bytecode AST optimization...");

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RemoveRedundantCode)) {
            return;
        }

        final AstOptimizer optimizer = new AstOptimizer();

        removeRedundantCode(method, context.getSettings());

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.ReduceBranchInstructionSet)) {
            return;
        }

        introducePreIncrementOptimization(context, method);

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            reduceBranchInstructionSet(block);
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.InlineVariables)) {
            return;
        }

        final Inlining inliningPhase1 = new Inlining(context, method);

        while (inliningPhase1.inlineAllVariables()) {
            inliningPhase1.analyzeMethod();
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.CopyPropagation)) {
            return;
        }

        inliningPhase1.copyPropagation();

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RewriteFinallyBlocks)) {
            return;
        }

        rewriteFinallyBlocks(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.SplitToMovableBlocks)) {
            return;
        }

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            optimizer.splitToMovableBlocks(block);
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RemoveUnreachableBlocks)) {
            return;
        }

        removeUnreachableBlocks(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.TypeInference)) {
            return;
        }

        TypeAnalysis.run(context, method);

        boolean done = false;

        LOG.fine("Performing block-level bytecode AST optimizations (enable FINER for more detail)...");

        int blockNumber = 0;

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            boolean modified;
            int blockRound = 0;

            ++blockNumber;

            do {
                if (LOG.isLoggable(Level.FINER)) {
                    LOG.finer("Optimizing block #" + blockNumber + ", round " + ++blockRound + "...");
                }

                modified = false;

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RemoveInnerClassInitSecurityChecks)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new RemoveInnerClassInitSecurityChecksOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.PreProcessShortCircuitAssignments)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new PreProcessShortCircuitAssignmentsOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.SimplifyShortCircuit)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new SimplifyShortCircuitOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.JoinBranchConditions)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new JoinBranchConditionsOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.SimplifyTernaryOperator)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new SimplifyTernaryOperatorOptimization(context, method));
                modified |= runOptimization(block, new SimplifyTernaryOperatorRoundTwoOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.JoinBasicBlocks)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new JoinBasicBlocksOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.SimplifyLogicalNot)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new SimplifyLogicalNotOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.TransformObjectInitializers)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new TransformObjectInitializersOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.TransformArrayInitializers)) {
                    done = true;
                    break;
                }

                modified |= new Inlining(context, method, true).inlineAllInBlock(block);
                modified |= runOptimization(block, new TransformArrayInitializersOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.IntroducePostIncrement)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new IntroducePostIncrementOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.InlineConditionalAssignments)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new InlineConditionalAssignmentsOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.MakeAssignmentExpressions)) {
                    done = true;
                    break;
                }

                modified |= runOptimization(block, new MakeAssignmentExpressionsOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.InlineLambdas)) {
                    return;
                }

                modified |= runOptimization(block, new InlineLambdasOptimization(context, method));

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.InlineVariables2)) {
                    done = true;
                    break;
                }

                modified |= new Inlining(context, method, true).inlineAllInBlock(block);
                new Inlining(context, method).copyPropagation();

                if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.MergeDisparateObjectInitializations)) {
                    done = true;
                    break;
                }

                modified |= mergeDisparateObjectInitializations(context, block);
            }
            while (modified);
        }

        if (done) {
            return;
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.FindLoops)) {
            return;
        }

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            new LoopsAndConditions(context).findLoops(block);
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.FindConditions)) {
            return;
        }

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            new LoopsAndConditions(context).findConditions(block);
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.FlattenNestedMovableBlocks)) {
            return;
        }

        flattenBasicBlocks(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RemoveRedundantCode2)) {
            return;
        }

        removeRedundantCode(method, context.getSettings());

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.GotoRemoval)) {
            return;
        }

        new GotoRemoval().removeGotos(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.DuplicateReturns)) {
            return;
        }

        duplicateReturnStatements(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.ReduceIfNesting)) {
            return;
        }

        reduceIfNesting(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.GotoRemoval2)) {
            return;
        }

        new GotoRemoval().removeGotos(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.ReduceComparisonInstructionSet)) {
            return;
        }

        for (final Expression e : method.getChildrenAndSelfRecursive(Expression.class)) {
            reduceComparisonInstructionSet(e);
        }

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RecombineVariables)) {
            return;
        }

        recombineVariables(method);

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.RemoveRedundantCode3)) {
            return;
        }

        GotoRemoval.removeRedundantCode(
            method,
            GotoRemoval.OPTION_MERGE_ADJACENT_LABELS |
            GotoRemoval.OPTION_REMOVE_REDUNDANT_RETURNS
        );

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.CleanUpTryBlocks)) {
            return;
        }

        cleanUpTryBlocks(method);

        //
        // This final inlining pass is necessary because the DuplicateReturns step and the
        // introduction of ternary operators may open up additional inlining possibilities.
        //

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.InlineVariables3)) {
            return;
        }

        final Inlining inliningPhase3 = new Inlining(context, method, true);

        inliningPhase3.inlineAllVariables();

        if (!shouldPerformStep(abortBeforeStep, AstOptimizationStep.TypeInference2)) {
            return;
        }

        TypeAnalysis.reset(context, method);
        TypeAnalysis.run(context, method);

        LOG.fine("Finished bytecode AST optimization.");
    }

    private static boolean shouldPerformStep(final AstOptimizationStep abortBeforeStep, final AstOptimizationStep nextStep) {
        if (abortBeforeStep == nextStep) {
            return false;
        }

        if (nextStep.isBlockLevelOptimization()) {
            if (LOG.isLoggable(Level.FINER)) {
                LOG.finer("Performing block-level optimization: " + nextStep + ".");
            }
        }
        else {
            if (LOG.isLoggable(Level.FINE)) {
                LOG.fine("Performing optimization: " + nextStep + ".");
            }
        }

        return true;
    }

    // 

    private static void removeUnreachableBlocks(final Block method) {
        final BasicBlock entryBlock = first(ofType(method.getBody(), BasicBlock.class));
        final Set

    // 

    private static void cleanUpTryBlocks(final Block method) {
        for (final Block block : method.getChildrenAndSelfRecursive(Block.class)) {
            final List body = block.getBody();

            for (int i = 0; i < body.size(); i++) {
                if (body.get(i) instanceof TryCatchBlock) {
                    final TryCatchBlock tryCatch = (TryCatchBlock) body.get(i);

                    if (tryCatch.getTryBlock().getBody().isEmpty()) {
                        if (tryCatch.getFinallyBlock() == null || tryCatch.getFinallyBlock().getBody().isEmpty()) {
                            body.remove(i--);
                            continue;
                        }
                    }

                    if (tryCatch.getFinallyBlock() != null &&
                        tryCatch.getCatchBlocks().isEmpty()) {

                        if (tryCatch.getTryBlock().getBody().size() == 1 &&
                            tryCatch.getTryBlock().getBody().get(0) instanceof TryCatchBlock) {

                            final TryCatchBlock innerTryCatch = (TryCatchBlock) tryCatch.getTryBlock().getBody().get(0);

                            if (innerTryCatch.getFinallyBlock() == null) {
                                tryCatch.setTryBlock(innerTryCatch.getTryBlock());
                                tryCatch.getCatchBlocks().addAll(innerTryCatch.getCatchBlocks());
                            }
                        }
//                        else if (tryCatch.getFinallyBlock().getBody().isEmpty()) {
//                            body.remove(i);
//                            body.addAll(i, tryCatch.getTryBlock().getBody());
//                        }
                    }
                }
            }
        }
    }

    // 

    // 

    private static void rewriteFinallyBlocks(final Block method) {
        rewriteSynchronized(method);

        final List a = new ArrayList<>();
        final StrongBox v = new StrongBox<>();

        int endFinallyCount = 0;

        for (final TryCatchBlock tryCatch : method.getChildrenAndSelfRecursive(TryCatchBlock.class)) {
            final Block finallyBlock = tryCatch.getFinallyBlock();

            if (finallyBlock == null || finallyBlock.getBody().size() < 2) {
                continue;
            }

            final List body = finallyBlock.getBody();
            final List exceptionCopies = new ArrayList<>();
            final Node lastInFinally = last(finallyBlock.getBody());

            if (matchGetArguments(body.get(0), AstCode.Store, v, a) &&
                match(a.get(0), AstCode.LoadException)) {

                body.remove(0);
                exceptionCopies.add(v.get());

                if (body.isEmpty() || !matchLoadStore(body.get(0), v.get(), v)) {
                    v.set(null);
                }
                else {
                    exceptionCopies.add(v.get());
                }

                final Label endFinallyLabel;

                if (body.size() > 1 && body.get(body.size() - 2) instanceof Label) {
                    endFinallyLabel = (Label) body.get(body.size() - 2);
                }
                else {
                    endFinallyLabel = new Label();
                    endFinallyLabel.setName("EndFinally_" + endFinallyCount++);

                    body.add(body.size() - 1, endFinallyLabel);
                }

                for (final Block b : finallyBlock.getSelfAndChildrenRecursive(Block.class)) {
                    final List blockBody = b.getBody();

                    for (int i = 0; i < blockBody.size(); i++) {
                        final Node node = blockBody.get(i);

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

                            if (matchLoadStoreAny(node, exceptionCopies, v)) {
                                exceptionCopies.add(v.get());
                            }
                            else if (e != lastInFinally &&
                                     matchGetArguments(e, AstCode.AThrow, a) &&
                                     matchLoadAny(a.get(0), exceptionCopies)) {

                                e.setCode(AstCode.Goto);
                                e.setOperand(endFinallyLabel);
                                e.getArguments().clear();
                            }
                        }
                    }
                }

                if (body.size() >= 1 &&
                    matchGetArguments(body.get(body.size() - 1), AstCode.AThrow, a) &&
                    matchLoadAny(a.get(0), exceptionCopies)) {

                    body.set(body.size() - 1, new Expression(AstCode.EndFinally, null, Expression.MYSTERY_OFFSET));
                }
            }
        }
    }

    private static void rewriteSynchronized(final Block method) {
        final StrongBox lockInfoBox = new StrongBox<>();

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            final List body = block.getBody();

            for (int i = 0; i < body.size() - 1; i++) {
                if (matchLock(body, i, lockInfoBox) &&
                    i + lockInfoBox.get().operationCount < body.size() &&
                    body.get(i + lockInfoBox.get().operationCount) instanceof TryCatchBlock) {

                    final TryCatchBlock tryCatch = (TryCatchBlock) body.get(i + lockInfoBox.get().operationCount);

                    if (tryCatch.isSynchronized()) {
                        continue;
                    }

                    final Block finallyBlock = tryCatch.getFinallyBlock();

                    if (finallyBlock != null) {
                        final List finallyBody = finallyBlock.getBody();
                        final LockInfo lockInfo = lockInfoBox.get();

                        if (finallyBody.size() == 3 &&
                            matchUnlock(finallyBody.get(1), lockInfo)) {

                            if (rewriteSynchronizedCore(tryCatch, lockInfo.operationCount)) {
                                tryCatch.setSynchronized(true);
                            }
                            else {
                                final StrongBox v = new StrongBox<>();
                                final List lockCopies = new ArrayList<>();

                                if (lockInfo.lockCopy != null) {
                                    lockCopies.add(lockInfo.lockCopy);
                                }

                                for (final Expression e : tryCatch.getChildrenAndSelfRecursive(Expression.class)) {
                                    if (matchLoadAny(e, lockCopies)) {
                                        e.setOperand(lockInfo.lock);
                                    }
                                    else if (matchLoadStore(e, lockInfo.lock, v) &&
                                             v.get() != lockInfo.lock) {

                                        lockCopies.add(v.get());
                                    }
                                }
                            }

                            inlineLockAccess(tryCatch, body, lockInfo);
                        }
                    }
                }
            }
        }
    }

    private static boolean rewriteSynchronizedCore(final TryCatchBlock tryCatch, final int depth) {
        final Block tryBlock = tryCatch.getTryBlock();
        final List tryBody = tryBlock.getBody();

        final LockInfo lockInfo;
        final StrongBox lockInfoBox = new StrongBox<>();

    test:
        {
            switch (tryBody.size()) {
                case 0:
                    return false;
                case 1:
                    lockInfo = null;
                    break test;
            }

            if (matchLock(tryBody, 0, lockInfoBox)) {
                lockInfo = lockInfoBox.get();

                if (lockInfo.operationCount < tryBody.size() &&
                    tryBody.get(lockInfo.operationCount) instanceof TryCatchBlock) {

                    final TryCatchBlock nestedTry = (TryCatchBlock) tryBody.get(lockInfo.operationCount);
                    final Block finallyBlock = nestedTry.getFinallyBlock();

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

                    final List finallyBody = finallyBlock.getBody();

                    if (finallyBody.size() == 3 &&
                        matchUnlock(finallyBody.get(1), lockInfo) &&
                        rewriteSynchronizedCore(nestedTry, depth + 1)) {

                        tryCatch.setSynchronized(true);
                        inlineLockAccess(tryCatch, tryBody, lockInfo);

                        return true;
                    }
                }
            }
            else {
                lockInfo = null;
            }

            break test;
        }

        final boolean skipTrailingBranch = matchUnconditionalBranch(tryBody.get(tryBody.size() - 1));

        if (tryBody.size() < (skipTrailingBranch ? depth + 1 : depth)) {
            return false;
        }

        final int removeTail = tryBody.size() - (skipTrailingBranch ? 1 : 0);
        final List monitorExitNodes;

        if (removeTail > 0 &&
            tryBody.get(removeTail - 1) instanceof TryCatchBlock) {

            final TryCatchBlock innerTry = (TryCatchBlock) tryBody.get(removeTail - 1);
            final List innerTryBody = innerTry.getTryBlock().getBody();

            if (matchLock(innerTryBody, 0, lockInfoBox) &&
                rewriteSynchronizedCore(innerTry, depth)) {

                inlineLockAccess(tryCatch, tryBody, lockInfo);
                tryCatch.setSynchronized(true);

                return true;
            }

            final boolean skipInnerTrailingBranch = matchUnconditionalBranch(innerTryBody.get(innerTryBody.size() - 1));

            if (innerTryBody.size() < (skipInnerTrailingBranch ? depth + 1 : depth)) {
                return false;
            }

            final int innerRemoveTail = innerTryBody.size() - (skipInnerTrailingBranch ? 1 : 0);

            monitorExitNodes = innerTryBody.subList(innerRemoveTail - depth, innerRemoveTail);
        }
        else {
            monitorExitNodes = tryBody.subList(removeTail - depth, removeTail);
        }

        final boolean removeAll = all(
            monitorExitNodes,
            new Predicate() {
                @Override
                public boolean test(final Node node) {
                    return match(node, AstCode.MonitorExit);
                }
            }
        );

        if (removeAll) {
            //
            // Remove the monitorexit instructions that we've already found.  Thank you, SubList.clear().
            //
            monitorExitNodes.clear();

            if (!tryCatch.getCatchBlocks().isEmpty()) {
                final TryCatchBlock newTryCatch = new TryCatchBlock();

                newTryCatch.setTryBlock(tryCatch.getTryBlock());
                newTryCatch.getCatchBlocks().addAll(tryCatch.getCatchBlocks());

                tryCatch.getCatchBlocks().clear();
                tryCatch.setTryBlock(new Block(newTryCatch));
            }

            inlineLockAccess(tryCatch, tryBody, lockInfo);

            tryCatch.setSynchronized(true);

            return true;
        }

        return false;
    }

    private static void inlineLockAccess(final Node owner, final List body, final LockInfo lockInfo) {
        if (lockInfo == null || lockInfo.lockInit == null) {
            return;
        }

        boolean lockCopyUsed = false;

        final StrongBox a = new StrongBox<>();
        final List lockAccesses = new ArrayList<>();
        final Set lockAccessLoads = new HashSet<>();

        for (final Expression e : owner.getSelfAndChildrenRecursive(Expression.class)) {
            if (matchLoad(e, lockInfo.lock) && !lockAccessLoads.contains(e)) {

                //
                // The lock variable is used elsewhere; we can't remove it.
                //
                return;
            }

            if (lockInfo.lockCopy != null &&
                matchLoad(e, lockInfo.lockCopy) &&
                !lockAccessLoads.contains(e)) {

                lockCopyUsed = true;
            }
            else if ((matchGetArgument(e, AstCode.MonitorEnter, a) || matchGetArgument(e, AstCode.MonitorExit, a)) &&
                     (matchLoad(a.get(), lockInfo.lock) || lockInfo.lockCopy != null && matchLoad(a.get(), lockInfo.lockCopy))) {

                lockAccesses.add(e);
                lockAccessLoads.add(a.get());
            }
        }

        for (final Expression e : lockAccesses) {
            e.getArguments().set(0, lockInfo.lockInit.clone());
        }

        body.remove(lockInfo.lockStore);

        lockInfo.lockAcquire.getArguments().set(0, lockInfo.lockInit.clone());

        if (lockInfo.lockCopy != null && !lockCopyUsed) {
            body.remove(lockInfo.lockStoreCopy);
        }
    }

    // 

    // 

    @SuppressWarnings({ "ConstantConditions", "StatementWithEmptyBody" })
    static void removeRedundantCode(final Block method, final DecompilerSettings settings) {
        final Map labelReferenceCount = new IdentityHashMap<>();

        final List branchExpressions = method.getSelfAndChildrenRecursive(
            Expression.class,
            new Predicate() {
                @Override
                public boolean test(final Expression e) {
                    return e.isBranch();
                }
            }
        );

        for (final Expression e : branchExpressions) {
            for (final Label branchTarget : e.getBranchTargets()) {
                final MutableInteger referenceCount = labelReferenceCount.get(branchTarget);

                if (referenceCount == null) {
                    labelReferenceCount.put(branchTarget, new MutableInteger(1));
                }
                else {
                    referenceCount.increment();
                }
            }
        }

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            final List body = block.getBody();
            final List newBody = new ArrayList<>(body.size());

            for (int i = 0, n = body.size(); i < n; i++) {
                final Node node = body.get(i);
                final StrongBox

    // 

    private static void introducePreIncrementOptimization(final DecompilerContext context, final Block method) {
        final Inlining inlining = new Inlining(context, method);

        inlining.analyzeMethod();

        for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
            final List body = block.getBody();
            final MutableInteger position = new MutableInteger();

            for (; position.getValue() < body.size() - 1; position.increment()) {
                if (!introducePreIncrementForVariables(body, position) &&
                    !introducePreIncrementForStaticFields(body, position, inlining)) {

                    introducePreIncrementForInstanceFields(body, position, inlining);
                }
            }
        }
    }

    private static boolean introducePreIncrementForVariables(final List body, final MutableInteger position) {
        final int i = position.getValue();

        if (i >= body.size() - 1) {
            return false;
        }

        final Node node = body.get(i);
        final Node next = body.get(i + 1);

        final StrongBox v = new StrongBox<>();
        final StrongBox t = new StrongBox<>();
        final StrongBox d = new StrongBox<>();

        if (!(node instanceof Expression && next instanceof Expression)) {
            return false;
        }

        final Expression e = (Expression) node;
        final Expression n = (Expression) next;

        if (matchGetArgument(e, AstCode.Inc, v, t) &&
            matchGetOperand(t.get(), AstCode.LdC, Integer.class, d) &&
            Math.abs(d.get()) == 1 &&
            match(n, AstCode.Store) &&
            matchLoad(n.getArguments().get(0), v.get())) {

            n.getArguments().set(
                0,
                new Expression(AstCode.PreIncrement, d.get(), n.getArguments().get(0).getOffset(), n.getArguments().get(0))
            );

            body.remove(i);
            position.decrement();

            return true;
        }

        return false;
    }

    private static boolean introducePreIncrementForStaticFields(final List body, final MutableInteger position, final Inlining inlining) {
        final int i = position.getValue();

        if (i >= body.size() - 3) {
            return false;
        }

        final Node n1 = body.get(i);
        final Node n2 = body.get(i + 1);
        final Node n3 = body.get(i + 2);
        final Node n4 = body.get(i + 3);

        final StrongBox tAny = new StrongBox<>();
        final List a = new ArrayList<>();

        if (!matchGetArguments(n1, AstCode.Store, tAny, a)) {
            return false;
        }

        final Variable t = (Variable) tAny.get();

        if (!matchGetOperand(a.get(0), AstCode.GetStatic, tAny)) {
            return false;
        }

        final Variable u;
        final FieldReference f = (FieldReference) tAny.get();

        if (!(matchGetArguments(n2, AstCode.Store, tAny, a) &&
              (u = (Variable) tAny.get()) != null &&
              matchGetOperand(a.get(0), AstCode.LdC, tAny) &&
              tAny.get() instanceof Integer &&
              Math.abs((int) tAny.get()) == 1)) {

            return false;
        }

        final Variable v;
        final int amount = (int) tAny.get();

        if (matchGetArguments(n3, AstCode.Store, tAny, a) &&
            inlining.loadCounts.get(v = (Variable) tAny.get()).getValue() > 1 &&
            matchGetArguments(a.get(0), AstCode.Add, a) &&
            matchLoad(a.get(0), t) &&
            matchLoad(a.get(1), u) &&
            matchGetArguments(n4, AstCode.PutStatic, tAny, a) &&
            tAny.get() instanceof FieldReference &&
            StringUtilities.equals(f.getFullName(), ((FieldReference) tAny.get()).getFullName()) &&
            matchLoad(a.get(0), v)) {

            ((Expression) n3).getArguments().set(
                0,
                new Expression(AstCode.PreIncrement, amount, ((Expression) n1).getArguments().get(0).getOffset(), ((Expression) n1).getArguments().get(0))
            );

            body.remove(i);
            body.remove(i);
            body.remove(i + 1);
            position.decrement();

            return true;
        }

        return false;
    }

    private static boolean introducePreIncrementForInstanceFields(final List body, final MutableInteger position, final Inlining inlining) {
        final int i = position.getValue();

        if (i < 1 || i >= body.size() - 3) {
            return false;
        }

        //
        // +-------------------------------+
        // | n = load(o)                   |
        // | t = getfield(f, load(n))      |
        // | u = ldc(1)                    |
        // | v = add(load(t), load(u))     |
        // | putfield(f, load(n), load(v)) |
        // +-------------------------------+
        //   |
        //   |    +--------------------------------------------+
        //   +--> | v = postincrement(1, getfield(f, load(n))) |
        //        +--------------------------------------------+
        //
        //   == OR ==
        //
        // +---------------------------+
        // | n = ?                     |
        // | t = loadelement(o, n)     |
        // | u = ldc(1)                |
        // | v = add(load(t), load(u)) |
        // | storeelement(o, n, v)     |
        // +---------------------------+
        //   |
        //   |    +-----------------------------------------+
        //   +--> | v = postincrement(1, loadelement(o, n)) |
        //        +-----------------------------------------+
        //

        if (!(body.get(i) instanceof Expression &&
              body.get(i - 1) instanceof Expression &&
              body.get(i + 1) instanceof Expression &&
              body.get(i + 2) instanceof Expression &&
              body.get(i + 3) instanceof Expression)) {

            return false;
        }

        final Expression e0 = (Expression) body.get(i - 1);
        final Expression e1 = (Expression) body.get(i);

        final List a = new ArrayList<>();
        final StrongBox tVar = new StrongBox<>();

        if (!matchGetArguments(e0, AstCode.Store, tVar, a)) {
            return false;
        }

        final Variable n = tVar.get();
        final StrongBox unused = new StrongBox<>();
        final boolean field;

        if (!matchGetArguments(e1, AstCode.Store, tVar, a) ||
            !((field = match(a.get(0), AstCode.GetField)) ? matchGetArguments(a.get(0), AstCode.GetField, unused, a)
                                                          : matchGetArguments(a.get(0), AstCode.LoadElement, a)) ||
            !matchLoad(a.get(field ? 0 : 1), n)) {

            return false;
        }

        final Variable t = tVar.get();
        final Variable o = field ? null : (Variable) a.get(0).getOperand();
        final FieldReference f = field ? (FieldReference) unused.get() : null;
        final Expression e2 = (Expression) body.get(i + 1);
        final StrongBox amount = new StrongBox<>();

        if (!matchGetArguments(e2, AstCode.Store, tVar, a) ||
            !matchGetOperand(a.get(0), AstCode.LdC, Integer.class, amount) ||
            Math.abs(amount.get()) != 1) {

            return false;
        }

        final Variable u = tVar.get();

        //  v = add(load(t), load(u))
        //  putfield(field, load(n), load(v))

        final Expression e3 = (Expression) body.get(i + 2);

        if (!matchGetArguments(e3, AstCode.Store, tVar, a) ||
            tVar.get().isGenerated() && inlining.loadCounts.get(tVar.get()).getValue() <= 1 ||
            !matchGetArguments(a.get(0), AstCode.Add, a) ||
            !matchLoad(a.get(0), t) ||
            !matchLoad(a.get(1), u)) {

            return false;
        }

        final Variable v = tVar.get();
        final Expression e4 = (Expression) body.get(i + 3);

        if (!(field ? matchGetArguments(e4, AstCode.PutField, unused, a)
                    : matchGetArguments(e4, AstCode.StoreElement, a)) ||
            !(field ? StringUtilities.equals(f.getFullName(), ((FieldReference) unused.get()).getFullName())
                    : matchLoad(a.get(0), o)) ||
            !matchLoad(a.get(field ? 0 : 1), n) ||
            !matchLoad(a.get(field ? 1 : 2), v)) {

            return false;
        }

        final Expression newExpression = new Expression(
            AstCode.PreIncrement,
            amount.get(),
            e1.getArguments().get(0).getOffset(),
            e1.getArguments().get(0)
        );

        e3.getArguments().set(0, newExpression);

        body.remove(i);
        body.remove(i);
        body.remove(i + 1);

        position.decrement();

        return true;
    }

    // 

    // 

    private static void reduceBranchInstructionSet(final Block block) {
        final List body = block.getBody();

        for (int i = 0; i < body.size(); i++) {
            final Node node = body.get(i);

            if (!(node instanceof Expression)) {
                continue;
            }

            final Expression e = (Expression) node;
            final AstCode code;

            switch (e.getCode()) {
                case __TableSwitch:
                case __LookupSwitch:
                case Switch: {
                    e.getArguments().get(0).getRanges().addAll(e.getRanges());
                    e.getRanges().clear();
                    continue;
                }

                case __LCmp:
                case __FCmpL:
                case __FCmpG:
                case __DCmpL:
                case __DCmpG: {
                    if (i == body.size() - 1 || !(body.get(i + 1) instanceof Expression)) {
                        continue;
                    }

                    final Expression next = (Expression) body.get(i + 1);

                    switch (next.getCode()) {
                        case __IfEq:
                            code = AstCode.CmpEq;
                            break;
                        case __IfNe:
                            code = AstCode.CmpNe;
                            break;
                        case __IfLt:
                            code = AstCode.CmpLt;
                            break;
                        case __IfGe:
                            code = AstCode.CmpGe;
                            break;
                        case __IfGt:
                            code = AstCode.CmpGt;
                            break;
                        case __IfLe:
                            code = AstCode.CmpLe;
                            break;
                        default:
                            continue;
                    }

                    body.remove(i);
                    break;
                }

                case __IfEq:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpEq;
                    break;

                case __IfNe:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpNe;
                    break;

                case __IfLt:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpLt;
                    break;
                case __IfGe:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpGe;
                    break;
                case __IfGt:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpGt;
                    break;
                case __IfLe:
                    e.getArguments().add(new Expression(AstCode.LdC, 0, e.getOffset()));
                    code = AstCode.CmpLe;
                    break;

                case __IfICmpEq:
                    code = AstCode.CmpEq;
                    break;
                case __IfICmpNe:
                    code = AstCode.CmpNe;
                    break;
                case __IfICmpLt:
                    code = AstCode.CmpLt;
                    break;
                case __IfICmpGe:
                    code = AstCode.CmpGe;
                    break;
                case __IfICmpGt:
                    code = AstCode.CmpGt;
                    break;
                case __IfICmpLe:
                    code = AstCode.CmpLe;
                    break;
                case __IfACmpEq:
                    code = AstCode.CmpEq;
                    break;
                case __IfACmpNe:
                    code = AstCode.CmpNe;
                    break;

                case __IfNull:
                    e.getArguments().add(new Expression(AstCode.AConstNull, null, e.getOffset()));
                    code = AstCode.CmpEq;
                    break;
                case __IfNonNull:
                    e.getArguments().add(new Expression(AstCode.AConstNull, null, e.getOffset()));
                    code = AstCode.CmpNe;
                    break;

                default:
                    continue;
            }

            final Expression newExpression = new Expression(code, null, e.getOffset(), e.getArguments());

            body.set(i, new Expression(AstCode.IfTrue, e.getOperand(), newExpression.getOffset(), newExpression));
            newExpression.getRanges().addAll(e.getRanges());
        }
    }

    // 

    // 

    private final static class RemoveInnerClassInitSecurityChecksOptimization extends AbstractExpressionOptimization {
        protected RemoveInnerClassInitSecurityChecksOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public boolean run(final List body, final Expression head, final int position) {
            final StrongBox getClassArgument = new StrongBox<>();
            final StrongBox getClassArgumentVariable = new StrongBox<>();
            final StrongBox constructorTargetVariable = new StrongBox<>();
            final StrongBox constructorArgumentVariable = new StrongBox<>();
            final StrongBox constructor = new StrongBox<>();
            final StrongBox getClassMethod = new StrongBox<>();
            final List arguments = new ArrayList<>();

            if (position > 0) {
                final Node previous = body.get(position - 1);

                arguments.clear();

                if (matchGetArguments(head, AstCode.InvokeSpecial, constructor, arguments) &&
                    arguments.size() > 1 &&
                    matchGetOperand(arguments.get(0), AstCode.Load, constructorTargetVariable) &&
                    matchGetOperand(arguments.get(1), AstCode.Load, constructorArgumentVariable) &&
                    matchGetArgument(previous, AstCode.InvokeVirtual, getClassMethod, getClassArgument) &&
                    isGetClassMethod(getClassMethod.get()) &&
                    matchGetOperand(getClassArgument.get(), AstCode.Load, getClassArgumentVariable) &&
                    getClassArgumentVariable.get() == constructorArgumentVariable.get()) {

                    final TypeReference constructorTargetType = constructorTargetVariable.get().getType();
                    final TypeReference constructorArgumentType = constructorArgumentVariable.get().getType();

                    if (constructorTargetType != null && constructorArgumentType != null) {
                        final TypeDefinition resolvedConstructorTargetType = constructorTargetType.resolve();
                        final TypeDefinition resolvedConstructorArgumentType = constructorArgumentType.resolve();

                        if (resolvedConstructorTargetType != null &&
                            resolvedConstructorArgumentType != null &&
                            resolvedConstructorTargetType.isNested() &&
                            !resolvedConstructorTargetType.isStatic() &&
                            (!resolvedConstructorArgumentType.isNested() ||
                             isEnclosedBy(resolvedConstructorTargetType, resolvedConstructorArgumentType))) {

                            body.remove(position - 1);
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        private static boolean isGetClassMethod(final MethodReference method) {
            return method.getParameters().isEmpty() &&
                   StringUtilities.equals(method.getName(), "getClass");
        }

        private static boolean isEnclosedBy(final TypeReference innerType, final TypeReference outerType) {
            if (innerType == null) {
                return false;
            }

            for (TypeReference current = innerType.getDeclaringType();
                 current != null;
                 current = current.getDeclaringType()) {

                if (MetadataResolver.areEquivalent(current, outerType)) {
                    return true;
                }
            }

            final TypeDefinition resolvedInnerType = innerType.resolve();

            return resolvedInnerType != null &&
                   isEnclosedBy(resolvedInnerType.getBaseType(), outerType);
        }
    }

    // 

    // 

    private static void reduceComparisonInstructionSet(final Expression expression) {
        final List arguments = expression.getArguments();
        final Expression firstArgument = arguments.isEmpty() ? null : arguments.get(0);

        if (matchSimplifiableComparison(expression)) {
            arguments.clear();
            arguments.addAll(firstArgument.getArguments());
            expression.getRanges().addAll(firstArgument.getRanges());
        }

        if (matchReversibleComparison(expression)) {
            final AstCode reversedCode;

            switch (firstArgument.getCode()) {
                case CmpEq:
                    reversedCode = AstCode.CmpNe;
                    break;
                case CmpNe:
                    reversedCode = AstCode.CmpEq;
                    break;
                case CmpLt:
                    reversedCode = AstCode.CmpGe;
                    break;
                case CmpGe:
                    reversedCode = AstCode.CmpLt;
                    break;
                case CmpGt:
                    reversedCode = AstCode.CmpLe;
                    break;
                case CmpLe:
                    reversedCode = AstCode.CmpGt;
                    break;

                default:
                    throw ContractUtils.unreachable();
            }

            expression.setCode(reversedCode);
            expression.getRanges().addAll(firstArgument.getRanges());

            arguments.clear();
            arguments.addAll(firstArgument.getArguments());
        }
    }

    // 

    // 

    private void splitToMovableBlocks(final Block block) {
        final List basicBlocks = new ArrayList<>();

        final List body = block.getBody();
        final Object firstNode = firstOrDefault(body);

        final Label entryLabel;

        if (firstNode instanceof Label) {
            entryLabel = (Label) firstNode;
        }
        else {
            entryLabel = new Label();
            entryLabel.setName("Block_" + (_nextLabelIndex++));
        }

        BasicBlock basicBlock = new BasicBlock();
        List basicBlockBody = basicBlock.getBody();

        basicBlocks.add(basicBlock);
        basicBlockBody.add(entryLabel);

        block.setEntryGoto(new Expression(AstCode.Goto, entryLabel, Expression.MYSTERY_OFFSET));

        if (!body.isEmpty()) {
            if (body.get(0) != entryLabel) {
                basicBlockBody.add(body.get(0));
            }

            for (int i = 1; i < body.size(); i++) {
                final Node lastNode = body.get(i - 1);
                final Node currentNode = body.get(i);

                //
                // Start a new basic block if necessary.
                //
                if (currentNode instanceof Label ||
                    currentNode instanceof TryCatchBlock ||
                    lastNode.isConditionalControlFlow() ||
                    lastNode.isUnconditionalControlFlow()) {

                    //
                    // Try to reuse the label.
                    //
                    final Label label = currentNode instanceof Label ? (Label) currentNode
                                                                     : new Label("Block_" + (_nextLabelIndex++));

                    //
                    // Terminate the last block.
                    //
                    if (!lastNode.isUnconditionalControlFlow()) {
                        basicBlockBody.add(new Expression(AstCode.Goto, label, Expression.MYSTERY_OFFSET));
                    }

                    //
                    // Start the new block.
                    //
                    basicBlock = new BasicBlock();
                    basicBlocks.add(basicBlock);
                    basicBlockBody = basicBlock.getBody();
                    basicBlockBody.add(label);

                    //
                    // Add the node to the basic block.
                    //
                    if (currentNode != label) {
                        basicBlockBody.add(currentNode);
                    }

                    if (currentNode instanceof TryCatchBlock) {
                        //
                        // If we have a TryCatchBlock with all nested blocks exiting to the same label,
                        // go ahead and insert an explicit jump to that label.  This prevents us from
                        // potentially adding a jump to the next node that would never actually be followed
                        // (possibly throwing a wrench in FindLoopsAndConditions later).
                        //
                        final Label exitLabel = checkExit(currentNode);

                        if (exitLabel != null) {
                            body.add(i + 1, new Expression(AstCode.Goto, exitLabel, Expression.MYSTERY_OFFSET));
                        }
                    }
                }
                else {
                    basicBlockBody.add(currentNode);
                }
            }
        }

        body.clear();
        body.addAll(basicBlocks);
    }

    private Label checkExit(final Node node) {
        if (node == null) {
            return null;
        }

        if (node instanceof BasicBlock) {
            return checkExit(lastOrDefault(((BasicBlock) node).getBody()));
        }

        if (node instanceof TryCatchBlock) {
            final TryCatchBlock tryCatch = (TryCatchBlock) node;
            final Label exitLabel = checkExit(lastOrDefault(tryCatch.getTryBlock().getBody()));

            if (exitLabel == null) {
                return null;
            }

            for (final CatchBlock catchBlock : tryCatch.getCatchBlocks()) {
                if (checkExit(lastOrDefault(catchBlock.getBody())) != exitLabel) {
                    return null;
                }
            }

            final Block finallyBlock = tryCatch.getFinallyBlock();

            if (finallyBlock != null && checkExit(lastOrDefault(finallyBlock.getBody())) != exitLabel) {
                return null;
            }

            return exitLabel;
        }

        if (node instanceof Expression) {
            final Expression expression = (Expression) node;
            final AstCode code = expression.getCode();

            if (code == AstCode.Goto) {
                return (Label) expression.getOperand();
            }
        }

        return null;
    }

    // 

    // 

    private static final class SimplifyShortCircuitOptimization extends AbstractBasicBlockOptimization {
        public SimplifyShortCircuitOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public final boolean run(final List body, final BasicBlock head, final int position) {
            assert body.contains(head);

            final StrongBox condition = new StrongBox<>();
            final StrongBox

    // 

    private static final class PreProcessShortCircuitAssignmentsOptimization extends AbstractBasicBlockOptimization {
        public PreProcessShortCircuitAssignmentsOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public final boolean run(final List body, final BasicBlock head, final int position) {
            assert body.contains(head);

            final StrongBox condition = new StrongBox<>();
            final StrongBox

    // 

    private static final class InlineConditionalAssignmentsOptimization extends AbstractBasicBlockOptimization {
        public InlineConditionalAssignmentsOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public final boolean run(final List body, final BasicBlock head, final int position) {
            assert body.contains(head);

            final StrongBox condition = new StrongBox<>();
            final StrongBox

    // 

    private final static class JoinBasicBlocksOptimization extends AbstractBasicBlockOptimization {
        protected JoinBasicBlocksOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public final boolean run(final List body, final BasicBlock head, final int position) {
            final StrongBox

    // 

    private final static class SimplifyTernaryOperatorOptimization extends AbstractBasicBlockOptimization {
        protected SimplifyTernaryOperatorOptimization(final DecompilerContext context, final Block method) {
            super(context, method);
        }

        @Override
        public final boolean run(final List body, final BasicBlock head, final int position) {
            final StrongBox condition = new StrongBox<>();
            final StrongBox