Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.strobel.decompiler.ast.AstOptimizer Maven / Gradle / Ivy
/*
* 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 liveLabels = new LinkedHashSet<>();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
final Map> embeddedLabels = new DefaultMap<>(
new Supplier>() {
@Override
public List get() {
return new ArrayList<>();
}
}
);
for (final BasicBlock basicBlock : method.getChildrenAndSelfRecursive(BasicBlock.class)) {
for (final Label label : basicBlock.getChildrenAndSelfRecursive(Label.class)) {
embeddedLabels.get(basicBlock).add(label);
}
}
for (final Expression e : method.getChildrenAndSelfRecursive(Expression.class)) {
if (e.getOperand() instanceof Label) {
liveLabels.add((Label) e.getOperand());
}
else if (e.getOperand() instanceof Label[]) {
Collections.addAll(liveLabels, (Label[]) e.getOperand());
}
}
outer:
for (final BasicBlock basicBlock : method.getChildrenAndSelfRecursive(BasicBlock.class)) {
final List body = basicBlock.getBody();
final Label entryLabel = (Label) body.get(0);
if (basicBlock != entryBlock && !liveLabels.contains(entryLabel)) {
for (final Label label : embeddedLabels.get(basicBlock)) {
if (liveLabels.contains(label)) {
continue outer;
}
}
while (body.size() > 1) {
body.remove(body.size() - 1);
}
}
}
}
//
//
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 target = new StrongBox<>();
final List args = new ArrayList<>();
if (matchGetOperand(node, AstCode.Goto, target) &&
i + 1 < body.size() &&
body.get(i + 1) == target.get()) {
//
// Ignore the branch.
//
if (labelReferenceCount.get(target.get()).getValue() == 1) {
//
// Ignore the label as well.
//
i++;
}
}
else if (match(node, AstCode.Nop)) {
//
// Ignore NOP.
//
}
else if (match(node, AstCode.Load)) {
//
// Ignore empty load.
//
}
else if (matchGetArguments(node, AstCode.Pop, args)) {
final StrongBox variable = new StrongBox<>();
if (!matchGetOperand(args.get(0), AstCode.Load, variable)) {
throw new IllegalStateException("Pop should just have Load at this stage.");
}
//
// Best effort to move bytecode range to previous statement.
//
final StrongBox previousVariable = new StrongBox<>();
final StrongBox previousExpression = new StrongBox<>();
if (i - 1 >= 0 &&
matchGetArgument(body.get(i - 1), AstCode.Store, previousVariable, previousExpression) &&
previousVariable.get() == variable.get()) {
previousExpression.get().getRanges().addAll(((Expression) node).getRanges());
//
// Ignore POP.
//
}
}
else if (matchGetArguments(node, AstCode.Pop2, args)) {
final StrongBox v1 = new StrongBox<>();
final StrongBox v2 = new StrongBox<>();
final StrongBox pv1 = new StrongBox<>();
final StrongBox pe1 = new StrongBox<>();
if (args.size() == 1) {
if (!matchGetOperand(args.get(0), AstCode.Load, v1)) {
throw new IllegalStateException("Pop2 should just have Load arguments at this stage.");
}
if (!v1.get().getType().getSimpleType().isDoubleWord()) {
throw new IllegalStateException("Pop2 instruction has only one single-word operand.");
}
//
// Best effort to move bytecode range to previous statement.
//
if (i - 1 >= 0 &&
matchGetArgument(body.get(i - 1), AstCode.Store, pv1, pe1) &&
pv1.get() == v1.get()) {
pe1.get().getRanges().addAll(((Expression) node).getRanges());
//
// Ignore POP2.
//
}
}
else {
if (!matchGetOperand(args.get(0), AstCode.Load, v1) ||
!matchGetOperand(args.get(1), AstCode.Load, v2)) {
throw new IllegalStateException("Pop2 should just have Load arguments at this stage.");
}
//
// Best effort to move bytecode range to previous statement.
//
final StrongBox pv2 = new StrongBox<>();
final StrongBox pe2 = new StrongBox<>();
if (i - 2 >= 0 &&
matchGetArgument(body.get(i - 2), AstCode.Store, pv1, pe1) &&
pv1.get() == v1.get() &&
matchGetArgument(body.get(i - 1), AstCode.Store, pv2, pe2) &&
pv2.get() == v2.get()) {
pe1.get().getRanges().addAll(((Expression) node).getRanges());
pe2.get().getRanges().addAll(((Expression) node).getRanges());
//
// Ignore POP2.
//
}
}
}
else if (node instanceof Label) {
final Label label = (Label) node;
final MutableInteger referenceCount = labelReferenceCount.get(label);
if (referenceCount != null && referenceCount.getValue() > 0) {
newBody.add(label);
}
}
else if (node instanceof TryCatchBlock) {
final TryCatchBlock tryCatch = (TryCatchBlock) node;
if (!isEmptyTryCatch(tryCatch)) {
newBody.add(node);
}
}
else if (match(node, AstCode.Switch) && !settings.getRetainPointlessSwitches()) {
final Expression e = (Expression) node;
final Label[] targets = (Label[]) e.getOperand();
if (targets.length == 1) {
final Expression test = e.getArguments().get(0);
e.setCode(AstCode.Goto);
e.setOperand(targets[0]);
if (Inlining.canBeExpressionStatement(test)) {
newBody.add(test);
}
e.getArguments().clear();
}
newBody.add(node);
}
else {
newBody.add(node);
}
}
body.clear();
body.addAll(newBody);
}
//
// DUP removal.
//
for (final Expression e : method.getSelfAndChildrenRecursive(Expression.class)) {
final List arguments = e.getArguments();
for (int i = 0, n = arguments.size(); i < n; i++) {
final Expression argument = arguments.get(i);
switch (argument.getCode()) {
case Dup:
case Dup2:
case DupX1:
case DupX2:
case Dup2X1:
case Dup2X2:
final Expression firstArgument = argument.getArguments().get(0);
firstArgument.getRanges().addAll(argument.getRanges());
arguments.set(i, firstArgument);
break;
}
}
}
cleanUpTryBlocks(method);
}
private static boolean isEmptyTryCatch(final TryCatchBlock tryCatch) {
if (tryCatch.getFinallyBlock() != null && !tryCatch.getFinallyBlock().getBody().isEmpty()) {
return false;
}
final List body = tryCatch.getTryBlock().getBody();
if (body.isEmpty()) {
return true;
}
final StrongBox label = new StrongBox<>();
return body.size() == 3 &&
matchGetOperand(body.get(0), AstCode.Goto, label) &&
body.get(1) == label.get() &&
match(body.get(2), AstCode.EndFinally);
}
//
//
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 trueLabel = new StrongBox<>();
final StrongBox falseLabel = new StrongBox<>();
final StrongBox nextCondition = new StrongBox<>();
final StrongBox nextTrueLabel = new StrongBox<>();
final StrongBox nextFalseLabel = new StrongBox<>();
if (matchLastAndBreak(head, AstCode.IfTrue, trueLabel, condition, falseLabel)) {
for (int pass = 0; pass < 2; pass++) {
//
// On second pass, swap labels and negate expression of the first branch.
// It is slightly ugly, but much better than copy-pasting the whole block.
//
final Label nextLabel = pass == 0 ? trueLabel.get() : falseLabel.get();
final Label otherLabel = pass == 0 ? falseLabel.get() : trueLabel.get();
final boolean negate = pass == 1;
final BasicBlock next = labelToBasicBlock.get(nextLabel);
//
// Try to match short circuit operators, e.g.,:
//
// c = a || b
// c = a && b
//
if (body.contains(next) &&
next != head &&
labelGlobalRefCount.get(nextLabel).getValue() == 1 &&
matchSingleAndBreak(next, AstCode.IfTrue, nextTrueLabel, nextCondition, nextFalseLabel) &&
(otherLabel == nextFalseLabel.get() || otherLabel == nextTrueLabel.get())) {
//
// Create short circuit branch.
//
final Expression logicExpression;
if (otherLabel == nextFalseLabel.get()) {
logicExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalAnd,
negate ? new Expression(AstCode.LogicalNot, null, condition.get().getOffset(), condition.get()) : condition.get(),
nextCondition.get()
);
}
else {
logicExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalOr,
negate ? condition.get() : new Expression(AstCode.LogicalNot, null, condition.get().getOffset(), condition.get()),
nextCondition.get()
);
}
final List headBody = head.getBody();
removeTail(headBody, AstCode.IfTrue, AstCode.Goto);
headBody.add(new Expression(AstCode.IfTrue, nextTrueLabel.get(), logicExpression.getOffset(), logicExpression));
headBody.add(new Expression(AstCode.Goto, nextFalseLabel.get(), logicExpression.getOffset()));
labelGlobalRefCount.get(trueLabel.get()).decrement();
labelGlobalRefCount.get(falseLabel.get()).decrement();
//
// Remove the inlined branch from scope.
//
removeOrThrow(body, next);
return true;
}
}
}
return false;
}
}
//
//
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 trueLabel = new StrongBox<>();
final StrongBox falseLabel = new StrongBox<>();
if (matchLastAndBreak(head, AstCode.IfTrue, trueLabel, condition, falseLabel)) {
final StrongBox nextTrueLabel = new StrongBox<>();
final StrongBox nextFalseLabel = new StrongBox<>();
final StrongBox sourceVariable = new StrongBox<>();
final StrongBox assignedValue = new StrongBox<>();
final StrongBox equivalentLoad = new StrongBox<>();
final StrongBox left = new StrongBox<>();
final StrongBox right = new StrongBox<>();
boolean modified = false;
for (int pass = 0; pass < 2; pass++) {
//
// On second pass, swap labels and negate expression of the first branch.
// It is slightly ugly, but much better than copy-pasting the whole block.
//
final Label nextLabel = pass == 0 ? trueLabel.get() : falseLabel.get();
final Label otherLabel = pass == 0 ? falseLabel.get() : trueLabel.get();
final BasicBlock next = labelToBasicBlock.get(nextLabel);
final BasicBlock other = labelToBasicBlock.get(otherLabel);
//
// Try to match short circuit operators, e.g.,:
//
// c = a || b
// c = a && b
//
if (body.contains(next) &&
next != head &&
labelGlobalRefCount.get(nextLabel).getValue() == 1 &&
matchLastAndBreak(next, AstCode.IfTrue, nextTrueLabel, condition, nextFalseLabel) &&
(otherLabel == nextFalseLabel.get() || otherLabel == nextTrueLabel.get())) {
final List nextBody = next.getBody();
final List otherBody = other.getBody();
while (nextBody.size() > 3 &&
matchAssignment(nextBody.get(nextBody.size() - 3), assignedValue, equivalentLoad) &&
matchLoad(assignedValue.value, sourceVariable) &&
matchComparison(condition.value, left, right)) {
if (matchLoad(left.value, sourceVariable.value)) {
condition.value.getArguments().set(0, (Expression) nextBody.get(nextBody.size() - 3));
nextBody.remove(nextBody.size() - 3);
modified = true;
}
else if (matchLoad(right.value, sourceVariable.value) && !containsMatch(left.value, equivalentLoad.value)) {
condition.value.getArguments().set(1, (Expression) nextBody.get(nextBody.size() - 3));
nextBody.remove(nextBody.size() - 3);
modified = true;
}
else {
break;
}
}
final boolean modifiedNext = modified;
modified = false;
while (matchAssignmentAndConditionalBreak(other, assignedValue, condition, trueLabel, falseLabel, equivalentLoad) &&
matchLoad(assignedValue.value, sourceVariable) &&
matchComparison(condition.value, left, right)) {
if (matchLoad(left.value, sourceVariable.value)) {
condition.value.getArguments().set(0, (Expression) otherBody.get(otherBody.size() - 3));
otherBody.remove(otherBody.size() - 3);
modified = true;
}
else if (matchLoad(right.value, sourceVariable.value) && !containsMatch(left.value, equivalentLoad.value)) {
condition.value.getArguments().set(1, (Expression) otherBody.get(otherBody.size() - 3));
otherBody.remove(otherBody.size() - 3);
modified = true;
}
else {
break;
}
}
final boolean modifiedOther = modified;
if (modifiedNext || modifiedOther) {
final Inlining inlining = new Inlining(context, method);
if (modifiedNext) {
inlining.inlineAllInBasicBlock(next);
}
if (modifiedOther) {
inlining.inlineAllInBasicBlock(other);
}
return true;
}
return false;
}
}
}
return false;
}
}
//
//
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 trueLabel = new StrongBox<>();
final StrongBox falseLabel = new StrongBox<>();
if (matchLastAndBreak(head, AstCode.IfTrue, trueLabel, condition, falseLabel)) {
final StrongBox sourceVariable = new StrongBox<>();
final StrongBox assignedValue = new StrongBox<>();
final StrongBox equivalentLoad = new StrongBox<>();
final StrongBox left = new StrongBox<>();
final StrongBox right = new StrongBox<>();
final Label thenLabel = trueLabel.value;
final Label elseLabel = falseLabel.value;
final BasicBlock thenSuccessor = labelToBasicBlock.get(thenLabel);
final BasicBlock elseSuccessor = labelToBasicBlock.get(elseLabel);
boolean modified = false;
if (matchAssignmentAndConditionalBreak(elseSuccessor, assignedValue, condition, trueLabel, falseLabel, equivalentLoad) &&
matchLoad(assignedValue.value, sourceVariable) &&
matchComparison(condition.value, left, right)) {
final List b = elseSuccessor.getBody();
if (matchLoad(left.value, sourceVariable.value)) {
condition.value.getArguments().set(0, (Expression) b.get(b.size() - 3));
b.remove(b.size() - 3);
modified = true;
}
else if (matchLoad(right.value, sourceVariable.value) && !containsMatch(left.value, equivalentLoad.value)) {
condition.value.getArguments().set(1, (Expression) b.get(b.size() - 3));
b.remove(b.size() - 3);
modified = true;
}
}
if (matchAssignmentAndConditionalBreak(thenSuccessor, assignedValue, condition, trueLabel, falseLabel, equivalentLoad) &&
matchLoad(assignedValue.value, sourceVariable) &&
matchComparison(condition.value, left, right)) {
final List b = thenSuccessor.getBody();
if (matchLoad(left.value, sourceVariable.value)) {
condition.value.getArguments().set(0, (Expression) b.get(b.size() - 3));
b.remove(b.size() - 3);
modified = true;
}
else if (matchLoad(right.value, sourceVariable.value) && !containsMatch(left.value, equivalentLoad.value)) {
condition.value.getArguments().set(1, (Expression) b.get(b.size() - 3));
b.remove(b.size() - 3);
modified = true;
}
}
return modified;
}
return false;
}
}
//
//
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 nextLabel = new StrongBox<>();
final List headBody = head.getBody();
final BasicBlock nextBlock;
final Node secondToLast = CollectionUtilities.getOrDefault(headBody, headBody.size() - 2);
if (secondToLast != null &&
!secondToLast.isConditionalControlFlow() &&
matchGetOperand(headBody.get(headBody.size() - 1), AstCode.Goto, nextLabel) &&
labelGlobalRefCount.get(nextLabel.get()).getValue() == 1 &
(nextBlock = labelToBasicBlock.get(nextLabel.get())) != null &&
nextBlock != EMPTY_BLOCK &&
body.contains(nextBlock) &&
nextBlock.getBody().get(0) == nextLabel.get() &&
!CollectionUtilities.any(nextBlock.getBody(), Predicates.instanceOf(BasicBlock.class))) {
final Node secondInNext = getOrDefault(nextBlock.getBody(), 1);
if (secondInNext instanceof TryCatchBlock) {
final Block tryBlock = ((TryCatchBlock) secondInNext).getTryBlock();
final Node firstInTry = firstOrDefault(tryBlock.getBody());
if (firstInTry instanceof BasicBlock) {
final Node firstInTryBody = firstOrDefault(((BasicBlock) firstInTry).getBody());
if (firstInTryBody instanceof Label &&
labelGlobalRefCount.get(firstInTryBody).getValue() > 1) {
return false;
}
}
}
removeTail(headBody, AstCode.Goto);
nextBlock.getBody().remove(0);
headBody.addAll(nextBlock.getBody());
removeOrThrow(body, nextBlock);
return true;
}
return false;
}
}
//
//
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 trueLabel = new StrongBox<>();
final StrongBox falseLabel = new StrongBox<>();
final StrongBox trueVariable = new StrongBox<>();
final StrongBox trueExpression = new StrongBox<>();
final StrongBox trueFall = new StrongBox<>();
final StrongBox falseVariable = new StrongBox<>();
final StrongBox falseExpression = new StrongBox<>();
final StrongBox falseFall = new StrongBox<>();
final StrongBox unused = new StrongBox<>();
if (matchLastAndBreak(head, AstCode.IfTrue, trueLabel, condition, falseLabel) &&
labelGlobalRefCount.get(trueLabel.value).getValue() == 1 &&
labelGlobalRefCount.get(falseLabel.value).getValue() == 1 &&
body.contains(labelToBasicBlock.get(trueLabel.value)) &&
body.contains(labelToBasicBlock.get(falseLabel.value))) {
if (((matchSingleAndBreak(labelToBasicBlock.get(trueLabel.value), AstCode.Store, trueVariable, trueExpression, trueFall) &&
matchSingleAndBreak(labelToBasicBlock.get(falseLabel.value), AstCode.Store, falseVariable, falseExpression, falseFall) &&
trueVariable.value == falseVariable.value &&
trueFall.value == falseFall.value) ||
(matchSingle(labelToBasicBlock.get(trueLabel.value), AstCode.Return, unused, trueExpression) &&
matchSingle(labelToBasicBlock.get(falseLabel.value), AstCode.Return, unused, falseExpression)))) {
final boolean isStore = trueVariable.value != null;
final AstCode opCode = isStore ? AstCode.Store : AstCode.Return;
final TypeReference returnType = isStore ? trueVariable.value.getType() : context.getCurrentMethod().getReturnType();
final boolean returnTypeIsBoolean = TypeAnalysis.isBoolean(returnType);
final StrongBox leftBooleanValue = new StrongBox<>();
final StrongBox rightBooleanValue = new StrongBox<>();
final Expression newExpression;
// a ? true:false is equivalent to a
// a ? false:true is equivalent to !a
// a ? true : b is equivalent to a || b
// a ? b : true is equivalent to !a || b
// a ? b : false is equivalent to a && b
// a ? false : b is equivalent to !a && b
if (returnTypeIsBoolean &&
matchBooleanConstant(trueExpression.value, leftBooleanValue) &&
matchBooleanConstant(falseExpression.value, rightBooleanValue) &&
(leftBooleanValue.value && !rightBooleanValue.value ||
!leftBooleanValue.value && rightBooleanValue.value)) {
//
// It can be expressed as a trivial expression.
//
if (leftBooleanValue.value) {
newExpression = condition.value;
}
else {
newExpression = new Expression(AstCode.LogicalNot, null, condition.value.getOffset(), condition.value);
newExpression.setInferredType(BuiltinTypes.Boolean);
}
}
else if ((returnTypeIsBoolean || TypeAnalysis.isBoolean(falseExpression.value.getInferredType())) &&
matchBooleanConstant(trueExpression.value, leftBooleanValue)) {
//
// It can be expressed as a logical expression.
//
if (leftBooleanValue.value) {
newExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalOr,
condition.value,
falseExpression.value
);
}
else {
newExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalAnd,
new Expression(AstCode.LogicalNot, null, condition.value.getOffset(), condition.value),
falseExpression.value
);
}
}
else if ((returnTypeIsBoolean || TypeAnalysis.isBoolean(trueExpression.value.getInferredType())) &&
matchBooleanConstant(falseExpression.value, rightBooleanValue)) {
//
// It can be expressed as a logical expression.
//
if (rightBooleanValue.value) {
newExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalOr,
new Expression(AstCode.LogicalNot, null, condition.value.getOffset(), condition.value),
trueExpression.value
);
}
else {
newExpression = makeLeftAssociativeShortCircuit(
AstCode.LogicalAnd,
condition.value,
trueExpression.value
);
}
}
else {
//
// Ternary operator tends to create long, complicated return statements.
//
if (opCode == AstCode.Return) {
return false;
}
//
// Only simplify generated variables.
//
if (opCode == AstCode.Store && !trueVariable.value.isGenerated()) {
return false;
}
//
// Create ternary expression.
//
// Default behavior seems to be to invert the condition. Try to reverse it.
//
if (simplifyLogicalNotArgument(condition.value)) {
newExpression = new Expression(
AstCode.TernaryOp,
null,
condition.value.getOffset(),
condition.value,
falseExpression.value,
trueExpression.value
);
}
else {
newExpression = new Expression(
AstCode.TernaryOp,
null,
condition.value.getOffset(),
condition.value,
trueExpression.value,
falseExpression.value
);
}
}
final List headBody = head.getBody();
removeTail(headBody, AstCode.IfTrue, AstCode.Goto);
headBody.add(new Expression(opCode, trueVariable.value, newExpression.getOffset(), newExpression));
if (isStore) {
headBody.add(new Expression(AstCode.Goto, trueFall.value, trueFall.value.getOffset()));
}
//
// Remove the old basic blocks.
//
removeOrThrow(body, labelToBasicBlock.get(trueLabel.value));
removeOrThrow(body, labelToBasicBlock.get(falseLabel.value));
return true;
}
else {
final StrongBox innerTrue = new StrongBox<>();
final StrongBox innerFalse = new StrongBox<>();
final StrongBox trueBreak = new StrongBox<>();
final StrongBox falseBreak = new StrongBox<>();
final StrongBox intermediateJump = new StrongBox<>();
if (matchSingleAndBreak(labelToBasicBlock.get(trueLabel.value), AstCode.IfTrue, innerTrue, trueExpression, trueFall) &&
matchSingleAndBreak(labelToBasicBlock.get(falseLabel.value), AstCode.IfTrue, unused, falseExpression, falseFall) &&
unused.value == innerTrue.value &&
matchLast(labelToBasicBlock.get(falseFall.value), AstCode.Goto, innerFalse)) {
final StrongBox innerTrueExpression = new StrongBox<>();
final StrongBox innerFalseExpression = new StrongBox<>();
//
// (a ? b : c) ? d : e
//
if (labelGlobalRefCount.get(innerTrue.value).getValue() == 2 &&
labelGlobalRefCount.get(innerFalse.value).getValue() == 2 &&
matchSingleAndBreak(labelToBasicBlock.get(innerTrue.value), AstCode.Store, trueVariable, innerTrueExpression, trueBreak) &&
matchSingleAndBreak(labelToBasicBlock.get(innerFalse.value), AstCode.Store, falseVariable, innerFalseExpression, falseBreak) &&
trueVariable.value == falseVariable.value &&
trueFall.value == innerFalse.value &&
trueBreak.value == falseBreak.value) {
final Expression newCondition;
final Expression newExpression;
final boolean negateInner = simplifyLogicalNotArgument(trueExpression.value);
if (negateInner && !simplifyLogicalNotArgument(falseExpression.value)) {
final Expression newFalseExpression = new Expression(AstCode.LogicalNot, null, falseExpression.value.getOffset(), falseExpression.value);
newFalseExpression.getRanges().addAll(falseExpression.value.getRanges());
falseExpression.set(newFalseExpression);
}
if (simplifyLogicalNotArgument(condition.value)) {
newCondition = new Expression(
AstCode.TernaryOp,
null,
condition.value.getOffset(),
condition.value,
falseExpression.value,
trueExpression.value
);
}
else {
newCondition = new Expression(
AstCode.TernaryOp,
null,
condition.value.getOffset(),
condition.value,
trueExpression.value,
falseExpression.value
);
}
if (negateInner) {
newExpression = new Expression(
AstCode.TernaryOp,
null,
newCondition.getOffset(),
newCondition,
innerFalseExpression.value,
innerTrueExpression.value
);
}
else {
newExpression = new Expression(
AstCode.TernaryOp,
null,
newCondition.getOffset(),
newCondition,
innerTrueExpression.value,
innerFalseExpression.value
);
}
final List headBody = head.getBody();
removeTail(headBody, AstCode.IfTrue, AstCode.Goto);
headBody.add(new Expression(AstCode.Store, trueVariable.value, newExpression.getOffset(), newExpression));
headBody.add(new Expression(AstCode.Goto, trueBreak.value, trueBreak.value.getOffset()));
//
// Remove the old basic blocks.
//
removeOrThrow(body, labelToBasicBlock.get(trueLabel.value));
removeOrThrow(body, labelToBasicBlock.get(falseLabel.value));
removeOrThrow(body, labelToBasicBlock.get(falseFall.value));
removeOrThrow(body, labelToBasicBlock.get(innerTrue.value));
removeOrThrow(body, labelToBasicBlock.get(innerFalse.value));
return true;
}
//
// (a ? b : c)
//
if (matchSingleAndBreak(labelToBasicBlock.get(innerTrue.value), AstCode.Store, trueVariable, innerTrueExpression, trueBreak) &&
(matchSingleAndBreak(labelToBasicBlock.get(falseFall.value), AstCode.Store, falseVariable, innerFalseExpression, falseBreak) ||
(matchSimpleBreak(labelToBasicBlock.get(falseFall.value), intermediateJump) &&
matchSingleAndBreak(
labelToBasicBlock.get(intermediateJump.value),
AstCode.Store,
falseVariable,
innerFalseExpression,
falseBreak
))) &&
trueVariable.value == falseVariable.value &&
trueBreak.value == falseBreak.value) {
final List arguments = condition.value.getArguments();
final Expression oldCondition = condition.value.clone();
condition.value.setCode(AstCode.TernaryOp);
arguments.clear();
Collections.addAll(
arguments,
simplifyLogicalNot(oldCondition),
simplifyLogicalNot(trueExpression.value),
simplifyLogicalNot(falseExpression.value)
);
final List headBody = head.getBody();
((Expression) headBody.get(headBody.size() - 2)).setOperand(innerTrue.value);
if (matchSimpleBreak(labelToBasicBlock.get(falseFall.value), intermediateJump)) {
if (labelGlobalRefCount.get(falseFall.value).getValue() == 1) {
removeOrThrow(body, labelToBasicBlock.get(falseFall.value));
}
((Expression) headBody.get(headBody.size() - 1)).setOperand(intermediateJump.value);
}
else {
((Expression) headBody.get(headBody.size() - 1)).setOperand(falseFall.value);
}
if (labelGlobalRefCount.get(trueFall.value).getValue() == 1) {
removeOrThrow(body, labelToBasicBlock.get(trueFall.value));
}
removeOrThrow(body, labelToBasicBlock.get(trueLabel.value));
removeOrThrow(body, labelToBasicBlock.get(falseLabel.value));
return true;
}
}
}
}
return false;
}
}
//
//
private final static class SimplifyTernaryOperatorRoundTwoOptimization extends AbstractExpressionOptimization {
protected SimplifyTernaryOperatorRoundTwoOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
final BooleanBox modified = new BooleanBox();
final Expression simplified = simplify(head, modified);
if (simplified != head) {
body.set(position, simplified);
}
return modified.get();
}
private static Expression simplify(final Expression head, final BooleanBox modified) {
if (match(head, AstCode.TernaryOp)) {
return simplifyTernaryDirect(head);
}
final List arguments = head.getArguments();
for (int i = 0; i < arguments.size(); i++) {
final Expression argument = arguments.get(i);
final Expression simplified = simplify(argument, modified);
if (simplified != argument) {
arguments.set(i, simplified);
modified.set(true);
}
}
final AstCode opType = head.getCode();
if (opType != AstCode.CmpEq && opType != AstCode.CmpNe) {
return head;
}
final Boolean right = matchBooleanConstant(arguments.get(1));
if (right == null) {
return head;
}
final Expression ternary = arguments.get(0);
if (ternary.getCode() != AstCode.TernaryOp) {
return head;
}
final Boolean ifTrue = matchBooleanConstant(ternary.getArguments().get(1));
final Boolean ifFalse = matchBooleanConstant(ternary.getArguments().get(2));
if (ifTrue == null || ifFalse == null || ifTrue.equals(ifFalse)) {
return head;
}
final boolean invert = !ifTrue.equals(right) ^ opType == AstCode.CmpNe;
final Expression condition = ternary.getArguments().get(0);
condition.getRanges().addAll(ternary.getRanges());
modified.set(true);
return invert ? new Expression(AstCode.LogicalNot, null, condition.getOffset(), condition)
: condition;
}
private static Expression simplifyTernaryDirect(final Expression head) {
final List a = new ArrayList<>();
final StrongBox v;
final StrongBox left;
final StrongBox right;
//
// c ? (v = a) : (v = b) => v = c ? a : b;
//
if (matchGetArguments(head, AstCode.TernaryOp, a)) {
if (matchGetArgument(a.get(1), AstCode.Store, v = new StrongBox<>(), left = new StrongBox<>()) &&
matchStore(a.get(2), v.get(), right = new StrongBox<>())) {
final Expression condition = a.get(0);
final Expression leftValue = left.value;
final Expression rightValue = right.value;
final Expression newTernary = new Expression(
AstCode.TernaryOp,
null,
condition.getOffset(),
condition,
leftValue,
rightValue
);
head.setCode(AstCode.Store);
head.setOperand(v.get());
head.getArguments().clear();
head.getArguments().add(newTernary);
newTernary.getRanges().addAll(head.getRanges());
return head;
}
else {
final Boolean ifTrue = matchBooleanConstant(head.getArguments().get(1));
final Boolean ifFalse = matchBooleanConstant(head.getArguments().get(2));
if (ifTrue == null || ifFalse == null || ifTrue.equals(ifFalse)) {
return head;
}
final boolean invert = Boolean.FALSE.equals(ifTrue);
final Expression condition = head.getArguments().get(0);
condition.getRanges().addAll(head.getRanges());
return invert ? new Expression(AstCode.LogicalNot, null, condition.getOffset(), condition)
: condition;
}
}
return head;
}
}
//
//
private final static class SimplifyLogicalNotOptimization extends AbstractExpressionOptimization {
protected SimplifyLogicalNotOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public final boolean run(final List body, final Expression head, final int position) {
final BooleanBox modified = new BooleanBox();
final Expression simplified = simplifyLogicalNot(head, modified);
assert simplified == null;
return modified.get();
}
}
//
//
private final static class TransformObjectInitializersOptimization extends AbstractExpressionOptimization {
protected TransformObjectInitializersOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
if (position >= body.size() - 1) {
return false;
}
final StrongBox v = new StrongBox<>();
final StrongBox newObject = new StrongBox<>();
final StrongBox objectType = new StrongBox<>();
final StrongBox constructor = new StrongBox<>();
final List arguments = new ArrayList<>();
if (position < body.size() - 1 &&
matchGetArgument(head, AstCode.Store, v, newObject) &&
matchGetOperand(newObject.get(), AstCode.__New, objectType)) {
final Node next = body.get(position + 1);
if (matchGetArguments(next, AstCode.InvokeSpecial, constructor, arguments) &&
!arguments.isEmpty() &&
matchLoad(arguments.get(0), v.get())) {
final Expression initExpression = new Expression(AstCode.InitObject, constructor.get(), ((Expression) next).getOffset());
arguments.remove(0);
initExpression.getArguments().addAll(arguments);
initExpression.getRanges().addAll(((Expression) next).getRanges());
head.getArguments().set(0, initExpression);
body.remove(position + 1);
return true;
}
}
if (matchGetArguments(head, AstCode.InvokeSpecial, constructor, arguments) &&
constructor.get().isConstructor() &&
!arguments.isEmpty() &&
matchGetArgument(arguments.get(0), AstCode.Store, v, newObject) &&
matchGetOperand(newObject.get(), AstCode.__New, objectType)) {
final Expression initExpression = new Expression(AstCode.InitObject, constructor.get(), newObject.get().getOffset());
arguments.remove(0);
initExpression.getArguments().addAll(arguments);
initExpression.getRanges().addAll(head.getRanges());
body.set(position, new Expression(AstCode.Store, v.get(), initExpression.getOffset(), initExpression));
return true;
}
return false;
}
}
//
//
private static boolean mergeDisparateObjectInitializations(final DecompilerContext context, final Block method) {
final Inlining inlining = new Inlining(context, method);
final Map parentLookup = new IdentityHashMap<>();
final Map newExpressions = new IdentityHashMap<>();
final StrongBox variable = new StrongBox<>();
final StrongBox ctor = new StrongBox<>();
final List args = new ArrayList<>();
boolean anyChanged = false;
parentLookup.put(method, Node.NULL);
for (final Node node : method.getSelfAndChildrenRecursive(Node.class)) {
if (matchStore(node, variable, args) &&
match(single(args), AstCode.__New)) {
newExpressions.put(variable.get(), (Expression) node);
}
for (final Node child : node.getChildren()) {
if (parentLookup.containsKey(child)) {
throw Error.expressionLinkedFromMultipleLocations(child);
}
parentLookup.put(child, node);
}
}
for (final Expression e : method.getSelfAndChildrenRecursive(Expression.class)) {
if (matchGetArguments(e, AstCode.InvokeSpecial, ctor, args) &&
ctor.get().isConstructor() &&
args.size() > 0 &&
matchLoad(first(args), variable)) {
final Expression storeNew = newExpressions.get(variable.value);
if (storeNew != null &&
Inlining.count(inlining.storeCounts, variable.value) == 1) {
final Node parent = parentLookup.get(storeNew);
if (parent instanceof Block || parent instanceof BasicBlock) {
final List body;
if (parent instanceof Block) {
body = ((Block) parent).getBody();
}
else {
body = ((BasicBlock) parent).getBody();
}
boolean moveInitToNew = false;
if (parentLookup.get(e) == parent) {
final int newIndex = body.indexOf(storeNew);
final int initIndex = body.indexOf(e);
if (initIndex > newIndex) {
for (int i = newIndex + 1; i < initIndex; i++) {
if (references(body.get(i), variable.value)) {
moveInitToNew = true;
break;
}
}
}
}
final Expression toRemove = moveInitToNew ? e : storeNew;
final Expression toRewrite = moveInitToNew ? storeNew : e;
final List arguments = e.getArguments();
final Expression initExpression = new Expression(AstCode.InitObject, ctor.get(), storeNew.getOffset());
arguments.remove(0);
initExpression.getArguments().addAll(arguments);
initExpression.getRanges().addAll(e.getRanges());
body.remove(toRemove);
toRewrite.setCode(AstCode.Store);
toRewrite.setOperand(variable.value);
toRewrite.getArguments().clear();
toRewrite.getArguments().add(initExpression);
anyChanged = true;
}
}
}
}
return anyChanged;
}
//
//
private final static class TransformArrayInitializersOptimization extends AbstractExpressionOptimization {
protected TransformArrayInitializersOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
final StrongBox v = new StrongBox<>();
final StrongBox newArray = new StrongBox<>();
if (matchGetArgument(head, AstCode.Store, v, newArray) &&
match(newArray.get(), AstCode.InitArray)) {
return tryRefineArrayInitialization(body, head, position);
}
final StrongBox v3 = new StrongBox<>();
final StrongBox elementType = new StrongBox<>();
final StrongBox lengthExpression = new StrongBox<>();
final StrongBox arrayLength = new StrongBox<>();
if (matchGetArgument(head, AstCode.Store, v, newArray) &&
matchGetArgument(newArray.get(), AstCode.NewArray, elementType, lengthExpression) &&
matchGetOperand(lengthExpression.get(), AstCode.LdC, Number.class, arrayLength) &&
arrayLength.get().intValue() > 0) {
final int actualArrayLength = arrayLength.get().intValue();
final StrongBox arrayPosition = new StrongBox<>();
final List initializers = new ArrayList<>();
int instructionsToRemove = 0;
for (int j = position + 1; j < body.size(); j++) {
final Node node = body.get(j);
if (!(node instanceof Expression)) {
continue;
}
final Expression next = (Expression) node;
if (next.getCode() == AstCode.StoreElement &&
matchGetOperand(next.getArguments().get(0), AstCode.Load, v3) &&
v3.get() == v.get() &&
matchGetOperand(next.getArguments().get(1), AstCode.LdC, Number.class, arrayPosition) &&
arrayPosition.get().intValue() >= initializers.size() &&
!next.getArguments().get(2).containsReferenceTo(v3.get())) {
while (initializers.size() < arrayPosition.get().intValue()) {
initializers.add(new Expression(AstCode.DefaultValue, elementType.get(), next.getOffset()));
}
initializers.add(next.getArguments().get(2));
instructionsToRemove++;
}
else {
break;
}
}
if (initializers.size() < actualArrayLength &&
initializers.size() >= actualArrayLength / 2) {
//
// Some compilers like Eclipse emit sparse array initializers. If at least half
// of the elements in the array are initialized, emit an InitArray expression.
// I think this is a reasonable threshold.
//
while (initializers.size() < actualArrayLength) {
initializers.add(new Expression(AstCode.DefaultValue, elementType.get(), head.getOffset()));
}
}
if (initializers.size() == actualArrayLength) {
final TypeReference arrayType = elementType.get().makeArrayType();
head.getArguments().set(0, new Expression(AstCode.InitArray, arrayType, head.getOffset(), initializers));
for (int i = 0; i < instructionsToRemove; i++) {
body.remove(position + 1);
}
new Inlining(context, method).inlineIfPossible(body, new MutableInteger(position));
return true;
}
}
return false;
}
private boolean tryRefineArrayInitialization(final List body, final Expression head, final int position) {
final StrongBox v = new StrongBox<>();
final List a = new ArrayList<>();
final StrongBox arrayType = new StrongBox<>();
if (matchGetArguments(head, AstCode.Store, v, a) &&
matchGetArguments(a.get(0), AstCode.InitArray, arrayType, a)) {
final Expression initArray = head.getArguments().get(0);
final List initializers = initArray.getArguments();
final int actualArrayLength = initializers.size();
final StrongBox arrayPosition = new StrongBox<>();
for (int j = position + 1; j < body.size(); j++) {
final Node node = body.get(j);
if (matchGetArguments(node, AstCode.StoreElement, a) &&
matchLoad(a.get(0), v.get()) &&
!a.get(2).containsReferenceTo(v.get()) &&
matchGetOperand(a.get(1), AstCode.LdC, Integer.class, arrayPosition) &&
arrayPosition.get() >= 0 &&
arrayPosition.get() < actualArrayLength &&
match(initializers.get(arrayPosition.get()), AstCode.DefaultValue)) {
initializers.set(arrayPosition.get(), a.get(2));
body.remove(j--);
}
else {
break;
}
}
}
return false;
}
}
//
//
private final static class MakeAssignmentExpressionsOptimization extends AbstractExpressionOptimization {
protected MakeAssignmentExpressionsOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
//
// ev = ...; store(v, ev) => ev = store(v, ...)
//
final StrongBox ev = new StrongBox<>();
final StrongBox initializer = new StrongBox<>();
final Node next = getOrDefault(body, position + 1);
final StrongBox v = new StrongBox<>();
final StrongBox storeArgument = new StrongBox<>();
if (matchGetArgument(head, AstCode.Store, ev, initializer) &&
!match(initializer.value, AstCode.__New)) {
if (matchGetArgument(next, AstCode.Store, v, storeArgument) &&
matchLoad(storeArgument.get(), ev.get())) {
final Expression nextExpression = (Expression) next;
final Node store2 = getOrDefault(body, position + 2);
if (canConvertStoreToAssignment(store2, ev.get())) {
//
// e = ...; store(v1, e); anyStore(v2, e) => store(v1, anyStore(v2, ...)
//
final Inlining inlining = new Inlining(context, method);
final MutableInteger loadCounts = inlining.loadCounts.get(ev.get());
final MutableInteger storeCounts = inlining.storeCounts.get(ev.get());
if (loadCounts != null &&
loadCounts.getValue() == 2 &&
storeCounts != null &&
storeCounts.getValue() == 1) {
final Expression storeExpression = (Expression) store2;
body.remove(position + 2); // remove store2
body.remove(position); // remove ev = ...
nextExpression.getArguments().set(0, storeExpression);
storeExpression.getArguments().set(storeExpression.getArguments().size() - 1, initializer.get());
inlining.inlineIfPossible(body, new MutableInteger(position));
return true;
}
}
body.remove(position + 1); // remove store
nextExpression.getArguments().set(0, initializer.get());
((Expression) body.get(position)).getArguments().set(0, nextExpression);
return true;
}
if (match(next, AstCode.PutStatic)) {
final Expression nextExpression = (Expression) next;
//
// ev = ...; putstatic(f, ev) => ev = putstatic(f, ...)
//
if (matchLoad(nextExpression.getArguments().get(0), ev.get())) {
body.remove(position + 1); // remove putstatic
nextExpression.getArguments().set(0, initializer.get());
((Expression) body.get(position)).getArguments().set(0, nextExpression);
return true;
}
}
return false;
}
final StrongBox equivalentLoad = new StrongBox<>();
if (matchAssignment(head, initializer, equivalentLoad) &&
next instanceof Expression) {
if (equivalentLoad.get().getCode() == AstCode.GetField) {
final FieldReference field = (FieldReference) equivalentLoad.get().getOperand();
final FieldDefinition resolvedField = field != null ? field.resolve() : null;
if (resolvedField != null && resolvedField.isSynthetic()) {
return false;
}
}
final boolean isLoad = matchLoad(initializer.value, v);
final ArrayDeque agenda = new ArrayDeque<>();
agenda.push((Expression) next);
processNext:
while (!agenda.isEmpty()) {
final Expression e = agenda.removeFirst();
if (e.getCode().isShortCircuiting() || e.getCode().isStore() || e.getCode().isFieldWrite()) {
break;
}
final List arguments = e.getArguments();
for (int i = 0; i < arguments.size(); i++) {
final Expression a = arguments.get(i);
if (a.isEquivalentTo(equivalentLoad.value) ||
isLoad && matchLoad(a, v.get()) ||
(Inlining.hasNoSideEffect(initializer.get()) &&
a.isEquivalentTo(initializer.get()) &&
initializer.get().getInferredType() != null &&
MetadataHelper.isSameType(initializer.get().getInferredType(), a.getInferredType(), true))) {
arguments.set(i, head);
body.remove(position);
return true;
}
if (!Inlining.isSafeForInlineOver(a, head)) {
break processNext;
}
agenda.push(a);
}
}
}
return false;
}
private boolean canConvertStoreToAssignment(final Node store, final Variable variable) {
if (store instanceof Expression) {
final Expression storeExpression = (Expression) store;
switch (storeExpression.getCode()) {
case Store:
case PutStatic:
case PutField:
case StoreElement:
return matchLoad(lastOrDefault(storeExpression.getArguments()), variable);
}
}
return false;
}
}
//
//
private final static class IntroducePostIncrementOptimization extends AbstractExpressionOptimization {
protected IntroducePostIncrementOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
boolean modified = introducePostIncrementForVariables(body, head, position);
assert body.get(position) == head;
if (position > 0) {
final Expression newExpression = introducePostIncrementForInstanceFields(head, body.get(position - 1));
if (newExpression != null) {
modified = true;
body.remove(position);
new Inlining(context, method).inlineIfPossible(body, new MutableInteger(position - 1));
}
}
return modified;
}
private boolean introducePostIncrementForVariables(final List body, final Expression e, final int position) {
//
// Works for local variables and static fields:
//
// e = load(i); inc(i, 1) => e = postincrement(i, 1)
// --or--
// e = load(i); store(i, add(e, ldc(1)) => e = postincrement(i, 1)
//
final StrongBox variable = new StrongBox<>();
final StrongBox initializer = new StrongBox<>();
if (!matchGetArgument(e, AstCode.Store, variable, initializer) || !variable.get().isGenerated()) {
return false;
}
final Node next = getOrDefault(body, position + 1);
if (!(next instanceof Expression)) {
return false;
}
final Expression nextExpression = (Expression) next;
final AstCode loadCode = initializer.get().getCode();
final AstCode storeCode = nextExpression.getCode();
boolean recombineVariables = false;
switch (loadCode) {
case Load: {
if (storeCode != AstCode.Inc && storeCode != AstCode.Store) {
return false;
}
final Variable loadVariable = (Variable) initializer.get().getOperand();
final Variable storeVariable = (Variable) nextExpression.getOperand();
if (loadVariable != storeVariable) {
if (loadVariable.getOriginalVariable() != null &&
loadVariable.getOriginalVariable() == storeVariable.getOriginalVariable()) {
recombineVariables = true;
}
else {
return false;
}
}
break;
}
case GetStatic: {
if (storeCode != AstCode.PutStatic) {
return false;
}
final FieldReference initializerOperand = (FieldReference) initializer.get().getOperand();
final FieldReference nextOperand = (FieldReference) nextExpression.getOperand();
if (initializerOperand == null ||
nextOperand == null ||
!StringUtilities.equals(initializerOperand.getFullName(), nextOperand.getFullName())) {
return false;
}
break;
}
default: {
return false;
}
}
final Expression add = storeCode == AstCode.Inc ? nextExpression : nextExpression.getArguments().get(0);
final StrongBox incrementAmount = new StrongBox<>();
final AstCode incrementCode = getIncrementCode(add, incrementAmount);
if (incrementCode == AstCode.Nop || !(match(add, AstCode.Inc) || match(add.getArguments().get(0), AstCode.Load))) {
return false;
}
if (recombineVariables) {
replaceVariables(
method,
new Function() {
@Override
public Variable apply(final Variable old) {
return old == nextExpression.getOperand() ? (Variable) initializer.get().getOperand() : old;
}
}
);
}
e.getArguments().set(
0,
new Expression(incrementCode, incrementAmount.get(), initializer.get().getOffset(), initializer.get())
);
body.remove(position + 1);
return true;
}
@SuppressWarnings("UnusedParameters")
private Expression introducePostIncrementForInstanceFields(final Expression e, final Node previous) {
//
// t = getfield(field, load(p)); putfield(field, load(p), add(load(t), ldc(1)))
// => store(t, postincrement(1, load(field, load(p))))
//
// Also works for array elements:
//
// t = loadelement(T, load(p), load(i)); storeelement(T, load(p), load(i), add(load(t), ldc(1)))
// => store(t, postincrement(1, loadelement(load(p), load(i))))
//
if (!(previous instanceof Expression)) {
return null;
}
final Expression p = (Expression) previous;
final StrongBox t = new StrongBox<>();
final StrongBox initialValue = new StrongBox<>();
if (!matchGetArgument(p, AstCode.Store, t, initialValue) ||
initialValue.get().getCode() != AstCode.GetField && initialValue.get().getCode() != AstCode.LoadElement) {
return null;
}
final AstCode code = e.getCode();
final Variable tempVariable = t.get();
if (code != AstCode.PutField && code != AstCode.StoreElement) {
return null;
}
//
// Test that all arguments except the last are load (1 arg for fields, 2 args for arrays).
//
final List arguments = e.getArguments();
for (int i = 0, n = arguments.size() - 1; i < n; i++) {
if (arguments.get(i).getCode() != AstCode.Load) {
return null;
}
}
final StrongBox incrementAmount = new StrongBox<>();
final Expression add = arguments.get(arguments.size() - 1);
final AstCode incrementCode = getIncrementCode(add, incrementAmount);
if (incrementCode == AstCode.Nop) {
return null;
}
final List addArguments = add.getArguments();
if (!matchGetOperand(addArguments.get(0), AstCode.Load, t) || t.get() != tempVariable) {
return null;
}
if (e.getCode() == AstCode.PutField) {
if (initialValue.get().getCode() != AstCode.GetField) {
return null;
}
//
// There might be two different FieldReference instances, so we compare the field's signatures:
//
final FieldReference getField = (FieldReference) initialValue.get().getOperand();
final FieldReference setField = (FieldReference) e.getOperand();
if (!StringUtilities.equals(getField.getFullName(), setField.getFullName())) {
return null;
}
}
else if (initialValue.get().getCode() != AstCode.LoadElement) {
return null;
}
final List initialValueArguments = initialValue.get().getArguments();
assert (arguments.size() - 1 == initialValueArguments.size());
for (int i = 0, n = initialValueArguments.size(); i < n; i++) {
if (!matchLoad(initialValueArguments.get(i), (Variable) arguments.get(i).getOperand())) {
return null;
}
}
p.getArguments().set(0, new Expression(AstCode.PostIncrement, incrementAmount.get(), initialValue.get().getOffset(), initialValue.get()));
return p;
}
private AstCode getIncrementCode(final Expression add, final StrongBox incrementAmount) {
final AstCode incrementCode;
final Expression amountArgument;
final boolean decrement;
switch (add.getCode()) {
case Add: {
incrementCode = AstCode.PostIncrement;
amountArgument = add.getArguments().get(1);
decrement = false;
break;
}
case Sub: {
incrementCode = AstCode.PostIncrement;
amountArgument = add.getArguments().get(1);
decrement = true;
break;
}
case Inc: {
incrementCode = AstCode.PostIncrement;
amountArgument = add.getArguments().get(0);
decrement = false;
break;
}
default: {
return AstCode.Nop;
}
}
if (matchGetOperand(amountArgument, AstCode.LdC, incrementAmount) &&
!(incrementAmount.get() instanceof Float ||
incrementAmount.get() instanceof Double)) {
if (incrementAmount.get().longValue() == 1L || incrementAmount.get().longValue() == -1L) {
incrementAmount.set(
decrement ? -incrementAmount.get().intValue()
: incrementAmount.get().intValue()
);
return incrementCode;
}
}
return AstCode.Nop;
}
}
//
//
@SuppressWarnings("StatementWithEmptyBody")
private static void flattenBasicBlocks(final Node node) {
if (node instanceof Block) {
final Block block = (Block) node;
final List flatBody = new ArrayList<>();
for (final Node child : block.getChildren()) {
flattenBasicBlocks(child);
if (child instanceof BasicBlock) {
final BasicBlock childBasicBlock = (BasicBlock) child;
final Node firstChild = firstOrDefault(childBasicBlock.getBody());
final Node lastChild = lastOrDefault(childBasicBlock.getBody());
if (!(firstChild instanceof Label)) {
throw new IllegalStateException("Basic block must start with a label.");
}
if (lastChild instanceof Expression && !lastChild.isUnconditionalControlFlow()) {
throw new IllegalStateException("Basic block must end with an unconditional branch.");
}
flatBody.addAll(childBasicBlock.getBody());
}
else {
flatBody.add(child);
}
}
block.setEntryGoto(null);
block.getBody().clear();
block.getBody().addAll(flatBody);
}
else if (node != null) {
for (final Node child : node.getChildren()) {
flattenBasicBlocks(child);
}
}
}
//
//
private static void duplicateReturnStatements(final Block method) {
final List methodBody = method.getBody();
final Map nextSibling = new IdentityHashMap<>();
final StrongBox constant = new StrongBox<>();
final StrongBox localVariable = new StrongBox<>();
final StrongBox targetLabel = new StrongBox<>();
final List returnArguments = new ArrayList<>();
//
// Build navigation data.
//
for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
final List body = block.getBody();
for (int i = 0; i < body.size() - 1; i++) {
final Node current = body.get(i);
if (current instanceof Label) {
nextSibling.put(current, body.get(i + 1));
}
}
}
//
// Duplicate returns.
//
for (final Block block : method.getSelfAndChildrenRecursive(Block.class)) {
final List body = block.getBody();
for (int i = 0; i < body.size(); i++) {
final Node node = body.get(i);
if (matchGetOperand(node, AstCode.Goto, targetLabel)) {
//
// Skip extra labels.
//
while (nextSibling.get(targetLabel.get()) instanceof Label) {
targetLabel.accept((Label) nextSibling.get(targetLabel.get()));
}
//
// Inline return statement.
//
final Node target = nextSibling.get(targetLabel.get());
if (target != null &&
matchGetArguments(target, AstCode.Return, returnArguments)) {
if (returnArguments.isEmpty()) {
body.set(
i,
new Expression(AstCode.Return, null, Expression.MYSTERY_OFFSET)
);
}
else if (matchGetOperand(returnArguments.get(0), AstCode.Load, localVariable)) {
body.set(
i,
new Expression(AstCode.Return, null, Expression.MYSTERY_OFFSET, new Expression(AstCode.Load, localVariable.get(), Expression.MYSTERY_OFFSET))
);
}
else if (matchGetOperand(returnArguments.get(0), AstCode.LdC, constant)) {
body.set(
i,
new Expression(AstCode.Return, null, Expression.MYSTERY_OFFSET, new Expression(AstCode.LdC, constant.get(), Expression.MYSTERY_OFFSET))
);
}
}
else if (!methodBody.isEmpty() && methodBody.get(methodBody.size() - 1) == targetLabel.get()) {
//
// It exits the main method, so it is effectively a return.
//
body.set(i, new Expression(AstCode.Return, null, Expression.MYSTERY_OFFSET));
}
}
}
}
}
//
//
private static void reduceIfNesting(final Node node) {
if (node instanceof Block) {
final Block block = (Block) node;
final List blockBody = block.getBody();
for (int i = 0; i < blockBody.size(); i++) {
final Node n = blockBody.get(i);
if (!(n instanceof Condition)) {
continue;
}
final Condition condition = (Condition) n;
final Node trueEnd = lastOrDefault(condition.getTrueBlock().getBody());
final Node falseEnd = lastOrDefault(condition.getFalseBlock().getBody());
final boolean trueExits = trueEnd != null && trueEnd.isUnconditionalControlFlow();
final boolean falseExits = falseEnd != null && falseEnd.isUnconditionalControlFlow();
if (trueExits) {
//
// Move the false block after the condition.
//
blockBody.addAll(i + 1, condition.getFalseBlock().getChildren());
condition.setFalseBlock(new Block());
}
else if (falseExits) {
//
// Move the true block after the condition.
//
blockBody.addAll(i + 1, condition.getTrueBlock().getChildren());
condition.setTrueBlock(new Block());
}
//
// Eliminate empty true block.
//
if (condition.getTrueBlock().getChildren().isEmpty() && !condition.getFalseBlock().getChildren().isEmpty()) {
final Block temp = condition.getTrueBlock();
final Expression conditionExpression = condition.getCondition();
condition.setTrueBlock(condition.getFalseBlock());
condition.setFalseBlock(temp);
condition.setCondition(simplifyLogicalNot(new Expression(AstCode.LogicalNot, null, conditionExpression.getOffset(), conditionExpression)));
}
}
}
for (final Node child : node.getChildren()) {
if (child != null && !(child instanceof Expression)) {
reduceIfNesting(child);
}
}
}
//
//
private static void recombineVariables(final Block method) {
final Map map = new IdentityHashMap<>();
replaceVariables(
method,
new Function() {
@Override
public final Variable apply(final Variable v) {
final VariableDefinition originalVariable = v.getOriginalVariable();
if (originalVariable == null) {
return v;
}
Variable combinedVariable = map.get(originalVariable);
if (combinedVariable == null) {
map.put(originalVariable, v);
combinedVariable = v;
}
return combinedVariable;
}
}
);
}
//
//
private final static class InlineLambdasOptimization extends AbstractExpressionOptimization {
private final MutableInteger _lambdaCount = new MutableInteger();
protected InlineLambdasOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public boolean run(final List body, final Expression head, final int position) {
final StrongBox c = new StrongBox<>();
final List a = new ArrayList<>();
boolean modified = false;
for (final Expression e : head.getChildrenAndSelfRecursive(Expression.class)) {
if (matchGetArguments(e, AstCode.InvokeDynamic, c, a)) {
final Lambda lambda = tryInlineLambda(e, c.value);
if (lambda != null) {
modified = true;
}
}
}
return modified;
}
private Lambda tryInlineLambda(final Expression site, final DynamicCallSite callSite) {
final MethodReference bootstrapMethod = callSite.getBootstrapMethod();
if ("java/lang/invoke/LambdaMetafactory".equals(bootstrapMethod.getDeclaringType().getInternalName()) &&
(StringUtilities.equals("metafactory", bootstrapMethod.getName(), StringComparison.OrdinalIgnoreCase) ||
StringUtilities.equals("altMetafactory", bootstrapMethod.getName(), StringComparison.OrdinalIgnoreCase)) &&
callSite.getBootstrapArguments().size() >= 3 &&
callSite.getBootstrapArguments().get(1) instanceof MethodHandle) {
final MethodHandle targetMethodHandle = (MethodHandle) callSite.getBootstrapArguments().get(1);
final MethodReference targetMethod = targetMethodHandle.getMethod();
final MethodDefinition resolvedMethod = targetMethod.resolve();
if (resolvedMethod == null ||
resolvedMethod.getBody() == null ||
!resolvedMethod.isSynthetic() ||
!MetadataHelper.isEnclosedBy(resolvedMethod.getDeclaringType(), context.getCurrentType()) ||
(StringUtilities.equals(resolvedMethod.getFullName(), context.getCurrentMethod().getFullName()) &&
StringUtilities.equals(resolvedMethod.getSignature(), context.getCurrentMethod().getSignature()))) {
return null;
}
final TypeReference functionType = callSite.getMethodType().getReturnType();
final List methods = MetadataHelper.findMethods(
functionType,
MetadataFilters.matchName(callSite.getMethodName())
);
MethodReference functionMethod = null;
for (final MethodReference m : methods) {
final MethodDefinition r = m.resolve();
if (r != null && r.isAbstract() && !r.isStatic() && !r.isDefault()) {
functionMethod = r;
break;
}
}
if (functionMethod == null) {
return null;
}
final DecompilerContext innerContext = new DecompilerContext(context.getSettings());
innerContext.setCurrentType(resolvedMethod.getDeclaringType());
innerContext.setCurrentMethod(resolvedMethod);
final MethodBody methodBody = resolvedMethod.getBody();
final List parameters = resolvedMethod.getParameters();
final Variable[] parameterMap = new Variable[methodBody.getMaxLocals()];
final List nodes = new ArrayList<>();
final Block body = new Block();
final Lambda lambda = new Lambda(body, functionType);
lambda.setMethod(functionMethod);
lambda.setCallSite(callSite);
final List lambdaParameters = lambda.getParameters();
if (resolvedMethod.hasThis()) {
final Variable variable = new Variable();
variable.setName("this");
variable.setType(context.getCurrentMethod().getDeclaringType());
variable.setOriginalParameter(context.getCurrentMethod().getBody().getThisParameter());
parameterMap[0] = variable;
lambdaParameters.add(variable);
}
for (final ParameterDefinition p : parameters) {
final Variable variable = new Variable();
variable.setName(p.getName());
variable.setType(p.getParameterType());
variable.setOriginalParameter(p);
variable.setLambdaParameter(true);
parameterMap[p.getSlot()] = variable;
lambdaParameters.add(variable);
}
final List arguments = site.getArguments();
for (int i = 0; i < arguments.size(); i++) {
final Variable v = lambdaParameters.get(0);
v.setOriginalParameter(null);
v.setGenerated(true);
final Expression argument = arguments.get(i).clone();
nodes.add(new Expression(AstCode.Store, v, argument.getOffset(), argument));
lambdaParameters.remove(0);
}
arguments.clear();
nodes.addAll(AstBuilder.build(methodBody, true, innerContext));
body.getBody().addAll(nodes);
for (final Expression e : body.getSelfAndChildrenRecursive(Expression.class)) {
final Object operand = e.getOperand();
if (operand instanceof Variable) {
final Variable oldVariable = (Variable) operand;
if (oldVariable.isParameter() &&
oldVariable.getOriginalParameter().getMethod() == resolvedMethod) {
final Variable newVariable = parameterMap[oldVariable.getOriginalParameter().getSlot()];
if (newVariable != null) {
e.setOperand(newVariable);
}
}
}
}
AstOptimizer.optimize(innerContext, body, AstOptimizationStep.InlineVariables2);
final int lambdaId = _lambdaCount.increment().getValue();
final Set renamedLabels = new HashSet<>();
for (final Node n : body.getSelfAndChildrenRecursive()) {
if (n instanceof Label) {
final Label label = (Label) n;
if (renamedLabels.add(label)) {
label.setName(label.getName() + "_" + lambdaId);
}
continue;
}
if (!(n instanceof Expression)) {
continue;
}
final Expression e = (Expression) n;
final Object operand = e.getOperand();
if (operand instanceof Label) {
final Label label = (Label) operand;
if (renamedLabels.add(label)) {
label.setName(label.getName() + "_" + lambdaId);
}
}
else if (operand instanceof Label[]) {
for (final Label label : (Label[]) operand) {
if (renamedLabels.add(label)) {
label.setName(label.getName() + "_" + lambdaId);
}
}
}
if (match(e, AstCode.Return)) {
e.putUserData(AstKeys.PARENT_LAMBDA_BINDING, site);
}
}
site.setCode(AstCode.Bind);
site.setOperand(lambda);
final List ranges = site.getRanges();
for (final Expression e : lambda.getSelfAndChildrenRecursive(Expression.class)) {
ranges.addAll(e.getRanges());
}
return lambda;
}
return null;
}
}
//
//
private static final class JoinBranchConditionsOptimization extends AbstractBranchBlockOptimization {
public JoinBranchConditionsOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
protected boolean run(
final List body,
final BasicBlock branchBlock,
final Expression branchCondition,
final Label thenLabel,
final Label elseLabel,
final boolean negate) {
if (labelGlobalRefCount.get(elseLabel).getValue() != 1) {
return false;
}
final BasicBlock elseBlock = labelToBasicBlock.get(elseLabel);
if (matchSingleAndBreak(elseBlock, AstCode.IfTrue, label1, expression, label2)) {
final Label elseThenLabel = label1.get();
final Label elseElseLabel = label2.get();
final Expression elseCondition = expression.get();
return runCore(body, branchBlock, branchCondition, thenLabel, elseLabel, elseCondition, negate, elseThenLabel, elseElseLabel, false) ||
runCore(body, branchBlock, branchCondition, thenLabel, elseLabel, elseCondition, negate, elseElseLabel, elseThenLabel, true);
}
return false;
}
private boolean runCore(
final List body,
final BasicBlock branchBlock,
final Expression branchCondition,
final Label thenLabel,
final Label elseLabel,
final Expression elseCondition,
final boolean negateFirst,
final Label elseThenLabel,
final Label elseElseLabel,
final boolean negateSecond) {
final BasicBlock thenBlock = labelToBasicBlock.get(thenLabel);
final BasicBlock elseThenBlock = labelToBasicBlock.get(elseThenLabel);
BasicBlock alsoRemove = null;
Label alsoDecrement = null;
if (elseThenBlock != thenBlock) {
if (matchSimpleBreak(elseThenBlock, label1) &&
labelGlobalRefCount.get(label1.get()).getValue() <= 2) {
final BasicBlock intermediateBlock = labelToBasicBlock.get(label1.get());
if (intermediateBlock != thenBlock) {
return false;
}
alsoRemove = elseThenBlock;
alsoDecrement = label1.get();
}
else {
return false;
}
}
final BasicBlock elseBlock = labelToBasicBlock.get(elseLabel);
final Expression logicExpression = new Expression(
AstCode.LogicalOr,
null,
Expression.MYSTERY_OFFSET,
negateFirst ? simplifyLogicalNotArgument(branchCondition) ? branchCondition
: new Expression(AstCode.LogicalNot, null, branchCondition.getOffset(), branchCondition)
: branchCondition,
negateSecond ? simplifyLogicalNotArgument(elseCondition) ? elseCondition
: new Expression(AstCode.LogicalNot, null, elseCondition.getOffset(), elseCondition)
: elseCondition
);
final List branchBody = branchBlock.getBody();
removeTail(branchBody, AstCode.IfTrue, AstCode.Goto);
branchBody.add(new Expression(AstCode.IfTrue, thenLabel, logicExpression.getOffset(), logicExpression));
branchBody.add(new Expression(AstCode.Goto, elseElseLabel, Expression.MYSTERY_OFFSET));
labelGlobalRefCount.get(elseLabel).decrement();
labelGlobalRefCount.get(elseThenLabel).decrement();
body.remove(elseBlock);
if (alsoRemove != null) {
body.remove(alsoRemove);
}
if (alsoDecrement != null) {
labelGlobalRefCount.get(alsoDecrement).decrement();
}
return true;
}
}
//
//
private interface BasicBlockOptimization {
boolean run(final List body, final BasicBlock head, final int position);
}
private interface ExpressionOptimization {
boolean run(final List body, final Expression head, final int position);
}
@SuppressWarnings("ProtectedField")
private static abstract class AbstractBasicBlockOptimization implements BasicBlockOptimization {
protected final static BasicBlock EMPTY_BLOCK = new BasicBlock();
protected final Map labelGlobalRefCount = new DefaultMap<>(MutableInteger.SUPPLIER);
protected final Map labelToBasicBlock = new DefaultMap<>(Suppliers.forValue(EMPTY_BLOCK));
protected final DecompilerContext context;
protected final IMetadataResolver resolver;
protected final Block method;
protected AbstractBasicBlockOptimization(final DecompilerContext context, final Block method) {
this.context = VerifyArgument.notNull(context, "context");
this.resolver = context.getCurrentType().getResolver();
this.method = VerifyArgument.notNull(method, "method");
for (final Expression e : method.getSelfAndChildrenRecursive(Expression.class)) {
if (e.isBranch()) {
for (final Label target : e.getBranchTargets()) {
labelGlobalRefCount.get(target).increment();
}
}
}
for (final BasicBlock basicBlock : method.getSelfAndChildrenRecursive(BasicBlock.class)) {
for (final Node child : basicBlock.getChildren()) {
if (child instanceof Label) {
labelToBasicBlock.put((Label) child, basicBlock);
}
}
}
}
}
@SuppressWarnings("ProtectedField")
private static abstract class AbstractExpressionOptimization implements ExpressionOptimization {
protected final DecompilerContext context;
protected final MetadataSystem metadataSystem;
protected final Block method;
protected AbstractExpressionOptimization(final DecompilerContext context, final Block method) {
this.context = VerifyArgument.notNull(context, "context");
this.metadataSystem = MetadataSystem.instance();
this.method = VerifyArgument.notNull(method, "method");
}
}
private static boolean runOptimization(final Block block, final BasicBlockOptimization optimization) {
boolean modified = false;
final List body = block.getBody();
for (int i = body.size() - 1; i >= 0; i--) {
if (i < body.size() && optimization.run(body, (BasicBlock) body.get(i), i)) {
modified = true;
++i;
}
}
return modified;
}
private static boolean runOptimization(final Block block, final ExpressionOptimization optimization) {
boolean modified = false;
for (final Node node : block.getBody()) {
final BasicBlock basicBlock = (BasicBlock) node;
final List body = basicBlock.getBody();
for (int i = body.size() - 1; i >= 0; i--) {
if (i >= body.size()) {
continue;
}
final Node n = body.get(i);
if (n instanceof Expression && optimization.run(body, (Expression) n, i)) {
modified = true;
++i;
}
}
}
return modified;
}
private static abstract class AbstractBranchBlockOptimization extends AbstractBasicBlockOptimization {
protected final StrongBox expression = new StrongBox<>();
protected final StrongBox label1 = new StrongBox<>();
protected final StrongBox label2 = new StrongBox<>();
public AbstractBranchBlockOptimization(final DecompilerContext context, final Block method) {
super(context, method);
}
@Override
public final boolean run(final List body, final BasicBlock head, final int position) {
if (matchLastAndBreak(head, AstCode.IfTrue, label1, expression, label2)) {
final Label thenLabel = label1.get();
final Label elseLabel = label2.get();
final Expression condition = expression.get();
return run(body, head, condition, thenLabel, elseLabel, false) ||
run(body, head, condition, elseLabel, thenLabel, true);
}
return false;
}
protected abstract boolean run(
final List body,
final BasicBlock branchBlock,
final Expression branchCondition,
final Label thenLabel,
final Label elseLabel,
final boolean negate);
}
//
//
public static void replaceVariables(final Node node, final Function mapping) {
if (node instanceof Expression) {
final Expression expression = (Expression) node;
final Object operand = expression.getOperand();
if (operand instanceof Variable) {
expression.setOperand(mapping.apply((Variable) operand));
}
for (final Expression argument : expression.getArguments()) {
replaceVariables(argument, mapping);
}
}
else {
if (node instanceof CatchBlock) {
final CatchBlock catchBlock = (CatchBlock) node;
final Variable exceptionVariable = catchBlock.getExceptionVariable();
if (exceptionVariable != null) {
catchBlock.setExceptionVariable(mapping.apply(exceptionVariable));
}
}
for (final Node child : node.getChildren()) {
replaceVariables(child, mapping);
}
}
}
static void removeOrThrow(final Collection collection, final T item) {
if (!collection.remove(item)) {
throw new IllegalStateException("The item was not found in the collection.");
}
}
static void removeTail(final List body, final AstCode... codes) {
for (int i = 0; i < codes.length; i++) {
if (((Expression) body.get(body.size() - codes.length + i)).getCode() != codes[i]) {
throw new IllegalStateException("Tailing code does not match expected.");
}
}
//noinspection UnusedDeclaration
for (final AstCode code : codes) {
body.remove(body.size() - 1);
}
}
static Expression makeLeftAssociativeShortCircuit(final AstCode code, final Expression left, final Expression right) {
//
// Assuming that the inputs are already left-associative.
//
if (match(right, code)) {
//
// Find the leftmost logical expression.
//
Expression current = right;
while (match(current.getArguments().get(0), code)) {
current = current.getArguments().get(0);
}
final Expression newArgument = new Expression(code, null, left.getOffset(), left, current.getArguments().get(0));
newArgument.setInferredType(BuiltinTypes.Boolean);
current.getArguments().set(0, newArgument);
return right;
}
else {
final Expression newExpression = new Expression(code, null, left.getOffset(), left, right);
newExpression.setInferredType(BuiltinTypes.Boolean);
return newExpression;
}
}
private final static BooleanBox SCRATCH_BOOLEAN_BOX = new BooleanBox();
static Expression simplifyLogicalNot(final Expression expression) {
final Expression result = simplifyLogicalNot(expression, SCRATCH_BOOLEAN_BOX);
return result != null ? result : expression;
}
static Expression simplifyLogicalNot(final Expression expression, final BooleanBox modified) {
Expression a;
Expression e = expression;
//
// CmpEq(a, ldc, 0) becomes LogicalNot(a) if the inferred type for expression 'a' is boolean.
//
List arguments = e.getArguments();
final StrongBox b = new StrongBox<>();
final Expression operand = arguments.isEmpty() ? null : arguments.get(0);
if (e.getCode() == AstCode.CmpEq &&
TypeAnalysis.isBoolean(operand.getInferredType()) &&
matchBooleanConstant(a = arguments.get(1), b) &&
Boolean.FALSE.equals(b.get())) {
e.setCode(AstCode.LogicalNot);
e.getRanges().addAll(a.getRanges());
arguments.remove(1);
modified.set(true);
}
Expression result = null;
if (e.getCode() == AstCode.CmpNe &&
TypeAnalysis.isBoolean(operand.getInferredType()) &&
matchBooleanConstant(arguments.get(1), b) &&
Boolean.FALSE.equals(b.get())) {
modified.set(true);
return e.getArguments().get(0);
}
if (e.getCode() == AstCode.TernaryOp) {
final Expression condition = arguments.get(0);
if (match(condition, AstCode.LogicalNot)) {
final Expression temp = arguments.get(1);
arguments.set(0, condition.getArguments().get(0));
arguments.set(1, arguments.get(2));
arguments.set(2, temp);
}
}
while (e.getCode() == AstCode.LogicalNot) {
a = operand;
//
// Remove double negation.
//
if (a.getCode() == AstCode.LogicalNot) {
result = a.getArguments().get(0);
result.getRanges().addAll(e.getRanges());
result.getRanges().addAll(a.getRanges());
e = result;
arguments = e.getArguments();
}
else {
if (simplifyLogicalNotArgument(a)) {
result = e = a;
arguments = e.getArguments();
modified.set(true);
}
break;
}
}
for (int i = 0; i < arguments.size(); i++) {
a = simplifyLogicalNot(arguments.get(i), modified);
if (a != null) {
arguments.set(i, a);
modified.set(true);
}
}
return result;
}
static boolean simplifyLogicalNotArgument(final Expression e) {
if (!canSimplifyLogicalNotArgument(e)) {
return false;
}
final List arguments = e.getArguments();
switch (e.getCode()) {
case CmpEq:
case CmpNe:
case CmpLt:
case CmpGe:
case CmpGt:
case CmpLe:
e.setCode(e.getCode().reverse());
return true;
case LogicalNot:
final Expression a = arguments.get(0);
e.setCode(a.getCode());
e.setOperand(a.getOperand());
arguments.clear();
arguments.addAll(a.getArguments());
e.getRanges().addAll(a.getRanges());
return true;
case LogicalAnd:
case LogicalOr:
if (!simplifyLogicalNotArgument(arguments.get(0))) {
negate(arguments.get(0));
}
if (!simplifyLogicalNotArgument(arguments.get(1))) {
negate(arguments.get(1));
}
e.setCode(e.getCode().reverse());
return true;
case TernaryOp:
simplifyLogicalNotArgument(arguments.get(1));
simplifyLogicalNotArgument(arguments.get(2));
return true;
default:
return TypeAnalysis.isBoolean(e.getInferredType()) &&
negate(e);
}
}
private static boolean negate(final Expression e) {
if (TypeAnalysis.isBoolean(e.getInferredType())) {
final Expression copy = e.clone();
e.setCode(AstCode.LogicalNot);
e.setOperand(null);
e.getArguments().clear();
e.getArguments().add(copy);
return true;
}
return false;
}
private static boolean canSimplifyLogicalNotArgument(final Expression e) {
switch (e.getCode()) {
case CmpEq:
case CmpNe:
case CmpLt:
case CmpGe:
case CmpGt:
case CmpLe:
return true;
case LogicalNot:
return true;
case LogicalAnd:
case LogicalOr:
final List arguments = e.getArguments();
return canSimplifyLogicalNotArgument(arguments.get(0)) ||
canSimplifyLogicalNotArgument(arguments.get(1));
case TernaryOp:
return TypeAnalysis.isBoolean(e.getInferredType()) &&
canSimplifyLogicalNotArgument(e.getArguments().get(1)) &&
canSimplifyLogicalNotArgument(e.getArguments().get(2));
default:
return false;
}
}
static boolean references(final Node node, final Variable v) {
for (final Expression e : node.getSelfAndChildrenRecursive(Expression.class)) {
if (matchLoad(e, v)) {
return true;
}
}
return false;
}
private static boolean containsMatch(final Node node, final Expression pattern) {
for (final Expression e : node.getSelfAndChildrenRecursive(Expression.class)) {
if (e.isEquivalentTo(pattern)) {
return true;
}
}
return false;
}
//
}