package org.benf.cfr.reader.bytecode.analysis.opgraph.op3rewriters; import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; import org.benf.cfr.reader.bytecode.analysis.opgraph.InstrIndex; import org.benf.cfr.reader.bytecode.analysis.opgraph.Op03SimpleStatement; import org.benf.cfr.reader.bytecode.analysis.parse.Expression; import org.benf.cfr.reader.bytecode.analysis.parse.LValue; import org.benf.cfr.reader.bytecode.analysis.parse.Statement; import org.benf.cfr.reader.bytecode.analysis.parse.expression.ConditionalExpression; import org.benf.cfr.reader.bytecode.analysis.parse.expression.Literal; import org.benf.cfr.reader.bytecode.analysis.parse.expression.TernaryExpression; import org.benf.cfr.reader.bytecode.analysis.parse.literal.TypedLiteral; import org.benf.cfr.reader.bytecode.analysis.parse.lvalue.StackSSALabel; import org.benf.cfr.reader.bytecode.analysis.parse.statement.*; import org.benf.cfr.reader.bytecode.analysis.parse.utils.*; import org.benf.cfr.reader.bytecode.analysis.stack.StackEntry; import org.benf.cfr.reader.bytecode.analysis.types.RawJavaType; import org.benf.cfr.reader.util.*; import org.benf.cfr.reader.util.collections.Functional; import org.benf.cfr.reader.util.collections.ListFactory; import org.benf.cfr.reader.util.collections.SetFactory; import org.benf.cfr.reader.util.collections.SetUtil; import org.benf.cfr.reader.util.functors.Predicate; import java.util.Collections; import java.util.List; import java.util.Set; public class ConditionalRewriter { private static class IsForwardIf implements Predicate<Op03SimpleStatement> { @Override public boolean test(Op03SimpleStatement in) { if (!(in.getStatement() instanceof IfStatement)) return false; IfStatement ifStatement = (IfStatement) in.getStatement(); if (!ifStatement.getJumpType().isUnknown()) return false; if (in.getTargets().get(1).getIndex().compareTo(in.getIndex()) <= 0) return false; return true; } } public static void identifyNonjumpingConditionals(List<Op03SimpleStatement> statements, BlockIdentifierFactory blockIdentifierFactory) { boolean success; Set<Op03SimpleStatement> ignoreTheseJumps = SetFactory.newSet(); do { success = false; List<Op03SimpleStatement> forwardIfs = Functional.filter(statements, new IsForwardIf()); Collections.reverse(forwardIfs); for (Op03SimpleStatement forwardIf : forwardIfs) { if (considerAsTrivialIf(forwardIf, statements) || considerAsSimpleIf(forwardIf, statements, blockIdentifierFactory, ignoreTheseJumps) || considerAsDexIf(forwardIf, statements)) { success = true; } } } while (success); } private static boolean considerAsTrivialIf(Op03SimpleStatement ifStatement, List<Op03SimpleStatement> statements) { Op03SimpleStatement takenTarget = ifStatement.getTargets().get(1); Op03SimpleStatement notTakenTarget = ifStatement.getTargets().get(0); int idxTaken = statements.indexOf(takenTarget); int idxNotTaken = statements.indexOf(notTakenTarget); if (idxTaken != idxNotTaken + 1) return false; if (!(takenTarget.getStatement().getClass() == GotoStatement.class && notTakenTarget.getStatement().getClass() == GotoStatement.class && takenTarget.getTargets().get(0) == notTakenTarget.getTargets().get(0))) { return false; } notTakenTarget.replaceStatement(new CommentStatement("empty if block")); // Replace the not taken target with an 'empty if statement'. return false; } /* * Spot a rather perverse structure that only happens with DEX2Jar, as far as I can tell. * * if (x) goto b * (a:) * ....[1] [ LINEAR STATEMENTS (* or already discounted) ] * goto c * b: * ....[2] [ LINEAR STATEMENTS (*) ] * goto d * c: * ....[3] * * Can be replaced with * * if (!x) goto a * b: * ...[2] * goto d * a: * ....[1] * c: * ....[3] * * Which, in turn, may allow us to make some more interesting choices later. */ private static boolean considerAsDexIf(Op03SimpleStatement ifStatement, List<Op03SimpleStatement> statements) { Statement innerStatement = ifStatement.getStatement(); if (innerStatement.getClass() != IfStatement.class) { return false; } IfStatement innerIfStatement = (IfStatement) innerStatement; int startIdx = statements.indexOf(ifStatement); int bidx = statements.indexOf(ifStatement.getTargets().get(1)); if (bidx <= startIdx) return false; // shouldn't happen. InstrIndex startIndex = ifStatement.getIndex(); InstrIndex bIndex = ifStatement.getTargets().get(1).getIndex(); if (startIndex.compareTo(bIndex) >= 0) { return false; // likewise. Indicates we need a renumber. } int aidx = startIdx + 1; int cidx = findOverIdx(bidx, statements); if (cidx == -1) return false; int didx = findOverIdx(cidx, statements); if (didx == -1) return false; if (didx <= cidx) return false; Set<Op03SimpleStatement> permittedSources = SetFactory.newSet(ifStatement); if (!isRangeOnlyReachable(aidx, bidx, cidx, statements, permittedSources)) return false; if (!isRangeOnlyReachable(bidx, cidx, didx, statements, permittedSources)) return false; /* * Looks like a legitimate reordering - rewrite statements. Pick up the entire block, and * move as per above. */ List<Op03SimpleStatement> alist = statements.subList(aidx, bidx); List<Op03SimpleStatement> blist = statements.subList(bidx, cidx); /* * Nop out the last entry of alist. */ alist.get(alist.size() - 1).nopOut(); List<Op03SimpleStatement> ifTargets = ifStatement.getTargets(); // Swap targets. Op03SimpleStatement tgtA = ifTargets.get(0); Op03SimpleStatement tgtB = ifTargets.get(1); ifTargets.set(0, tgtB); ifTargets.set(1, tgtA); innerIfStatement.setCondition(innerIfStatement.getCondition().getNegated().simplify()); List<Op03SimpleStatement> acopy = ListFactory.newList(alist); blist.addAll(acopy); alist = statements.subList(aidx, bidx); alist.clear(); Cleaner.reindexInPlace(statements); return true; } private static int findOverIdx(int startNext, List<Op03SimpleStatement> statements) { /* * Find a forward goto before b. */ Op03SimpleStatement next = statements.get(startNext); Op03SimpleStatement cStatement = null; for (int gSearch = startNext - 1; gSearch >= 0; gSearch--) { Op03SimpleStatement stm = statements.get(gSearch); Statement s = stm.getStatement(); if (s instanceof Nop) continue; if (s.getClass() == GotoStatement.class) { Op03SimpleStatement tgtC = stm.getTargets().get(0); if (tgtC.getIndex().isBackJumpFrom(next)) return -1; cStatement = tgtC; break; } return -1; } if (cStatement == null) return -1; int cidx = statements.indexOf(cStatement); return cidx; } /* * Is this structured as * * .. * .. * .. * goto X * * Where the range is self-contained. */ private static boolean isRangeOnlyReachable(int startIdx, int endIdx, int tgtIdx, List<Op03SimpleStatement> statements, Set<Op03SimpleStatement> permittedSources) { Set<Op03SimpleStatement> reachable = SetFactory.newSet(); final Op03SimpleStatement startStatement = statements.get(startIdx); final Op03SimpleStatement endStatement = statements.get(endIdx); final Op03SimpleStatement tgtStatement = statements.get(tgtIdx); InstrIndex startIndex = startStatement.getIndex(); InstrIndex endIndex = endStatement.getIndex(); InstrIndex finalTgtIndex = tgtStatement.getIndex(); reachable.add(statements.get(startIdx)); boolean foundEnd = false; for (int idx = startIdx; idx < endIdx; ++idx) { Op03SimpleStatement stm = statements.get(idx); if (!reachable.contains(stm)) { return false; } // We don't expect this statement to have sources before startIndex or after bIndex. // We don't expect any raw gotos ( or targets ) after bIndex / beforeStartIndex. (break / continue are ok). for (Op03SimpleStatement source : stm.getSources()) { InstrIndex sourceIndex = source.getIndex(); if (sourceIndex.compareTo(startIndex) < 0) { if (!permittedSources.contains(source)) { return false; } } if (sourceIndex.compareTo(endIndex) >= 0) { return false; } } for (Op03SimpleStatement target : stm.getTargets()) { InstrIndex tgtIndex = target.getIndex(); if (tgtIndex.compareTo(startIndex) < 0) { return false; } if (tgtIndex.compareTo(endIndex) >= 0) { if (tgtIndex == finalTgtIndex) { foundEnd = true; } else { return false; } } reachable.add(target); } } return foundEnd; } /* * Handle this case * * ** if (!var7_4.isSuccess() || var7_4.getData() == null || var7_4.getData().size() <= 0) goto lbl-1000 lbl10: // 1 sources: var9_5 = var7_4.getData().get(0); var8_6 = false; if (var9_5 == null) lbl-1000: // 2 sources: { var8_6 = true; } * Here we can see that the earlier conditional jumps into the later one after the comparison - the later one * can be represented as a break out of an anonymous block covering the early one. It's a bit messy, but Dex2Jar * generates this kind of stuff. */ private static boolean detectAndRemarkJumpIntoOther(Set<BlockIdentifier> blocksAtStart, Set<BlockIdentifier> blocksAtEnd, Op03SimpleStatement realEnd, Op03SimpleStatement ifStatement) { if (blocksAtEnd.size() != blocksAtStart.size() + 1) return false; List<BlockIdentifier> diff = SetUtil.differenceAtakeBtoList(blocksAtEnd, blocksAtStart); BlockIdentifier testBlock = diff.get(0); if (testBlock.getBlockType() != BlockType.SIMPLE_IF_TAKEN) return false; // If the difference is a simple-if, AND the statement AFTER realEnd is the target of BOTH // realEnd AND the source if statement... AND the if statement is inside our potential range, // we can convert THAT to a block exit, and remove that conditional. List<Op03SimpleStatement> realEndTargets = realEnd.getTargets(); if (!(realEndTargets.size() == 1 && realEndTargets.get(0).getLinearlyPrevious() == realEnd)) return false; Op03SimpleStatement afterRealEnd = realEndTargets.get(0); List<Op03SimpleStatement> areSources = afterRealEnd.getSources(); if (areSources.size() != 2) return false; Op03SimpleStatement other = areSources.get(0) == realEnd ? areSources.get(1) : areSources.get(0); // If other is the conditional for testBlock, we can change that jump into a break, as long as // the new conditional is a labelled block. Statement otherStatement = other.getStatement(); if (!other.getIndex().isBackJumpTo(ifStatement)) return false; if (!(otherStatement instanceof IfStatement)) return false; Pair<BlockIdentifier, BlockIdentifier> knownBlocks = ((IfStatement) otherStatement).getBlocks(); if (!(knownBlocks.getFirst() == testBlock && knownBlocks.getSecond() == null)) return false; ((IfStatement) otherStatement).setJumpType(JumpType.BREAK_ANONYMOUS); // And we need to unmark anything which is in testBlock. Op03SimpleStatement current = other.getLinearlyNext(); while (current != null && current.getBlockIdentifiers().contains(testBlock)) { current.getBlockIdentifiers().remove(testBlock); current = current.getLinearlyNext(); } return true; } /* * This is an if statement where both targets are forward. * * it's a 'simple' if, if: * * target[0] reaches (incl) the instruction before target[1] without any jumps (other than continue / break). * * note that the instruction before target[1] doesn't have to have target[1] as a target... * (we might have if (foo) return;) * * If it's a SIMPLE if/else, then the last statement of the if block is a goto, which jumps to after the else * block. We don't want to keep that goto, as we've inferred structure now. * * We trim that GOTO when we move from an UnstructuredIf to a StructuredIf. */ private static boolean considerAsSimpleIf(Op03SimpleStatement ifStatement, List<Op03SimpleStatement> statements, BlockIdentifierFactory blockIdentifierFactory, Set<Op03SimpleStatement> ignoreTheseJumps) { Op03SimpleStatement takenTarget = ifStatement.getTargets().get(1); Op03SimpleStatement notTakenTarget = ifStatement.getTargets().get(0); int idxTaken = statements.indexOf(takenTarget); int idxNotTaken = statements.indexOf(notTakenTarget); IfStatement innerIfStatement = (IfStatement) ifStatement.getStatement(); Set<Op03SimpleStatement> ignoreLocally = SetFactory.newSet(); boolean takenAction = false; int idxCurrent = idxNotTaken; if (idxCurrent > idxTaken) return false; int idxEnd = idxTaken; int maybeElseEndIdx = -1; Op03SimpleStatement maybeElseEnd = null; boolean maybeSimpleIfElse = false; GotoStatement leaveIfBranchGoto = null; Op03SimpleStatement leaveIfBranchHolder = null; List<Op03SimpleStatement> ifBranch = ListFactory.newList(); List<Op03SimpleStatement> elseBranch = null; // Consider the try blocks we're in at this point. (the ifStatemenet). // If we leave any of them, we've left the if. Set<BlockIdentifier> blocksAtStart = ifStatement.getBlockIdentifiers(); if (idxCurrent == idxEnd) { // It's a trivial tautology? We can't nop it out unless it's side effect free. // Instead insert a comment. Op03SimpleStatement taken = new Op03SimpleStatement(blocksAtStart, new CommentStatement("empty if block"), notTakenTarget.getIndex().justBefore()); taken.addSource(ifStatement); taken.addTarget(notTakenTarget); Op03SimpleStatement emptyTarget = ifStatement.getTargets().get(0); if (notTakenTarget != emptyTarget) { notTakenTarget.addSource(taken); } emptyTarget.replaceSource(ifStatement, taken); ifStatement.getTargets().set(0, taken); statements.add(idxTaken, taken); BlockIdentifier ifBlockLabel = blockIdentifierFactory.getNextBlockIdentifier(BlockType.SIMPLE_IF_TAKEN); taken.markFirstStatementInBlock(ifBlockLabel); // O(N) insertion here is annoying, but we need to fix up the list IMMEDIATELY. taken.getBlockIdentifiers().add(ifBlockLabel); innerIfStatement.setKnownBlocks(ifBlockLabel, null); innerIfStatement.setJumpType(JumpType.GOTO_OUT_OF_IF); return true; } Set<Op03SimpleStatement> validForwardParents = SetFactory.newSet(); validForwardParents.add(ifStatement); /* * Find the (possible) end of the if block, which is a forward unconditional jump. * If that's the case, cache it and rewrite any jumps we see which go to the same place * as double jumps via this unconditional. */ final Op03SimpleStatement stmtLastBlock = statements.get(idxTaken - 1); Op03SimpleStatement stmtLastBlockRewrite = null; Statement stmtLastBlockInner = stmtLastBlock.getStatement(); if (stmtLastBlockInner.getClass() == GotoStatement.class) { stmtLastBlockRewrite = stmtLastBlock; } Op03SimpleStatement statementStart = statements.get(idxCurrent); Predicate<BlockIdentifier> tryBlockFilter = new Predicate<BlockIdentifier>() { @Override public boolean test(BlockIdentifier in) { return in.getBlockType() == BlockType.TRYBLOCK; } }; Set<BlockIdentifier> startTryBlocks = Functional.filterSet(statementStart.getBlockIdentifiers(), tryBlockFilter); Op03SimpleStatement statementPrev = statementStart; do { Op03SimpleStatement statementCurrent = statements.get(idxCurrent); boolean currentParent = false; if (idxCurrent > 0) { Op03SimpleStatement currentPrev = statements.get(idxCurrent-1); if (currentPrev.getTargets().contains(statementCurrent) && currentPrev.getStatement().fallsToNext()) currentParent = true; } /* Consider sources of this which jumped forward to get to it. * */ InstrIndex currentIndex = statementCurrent.getIndex(); if (!currentParent) { for (Op03SimpleStatement source : statementCurrent.getSources()) { if (currentIndex.isBackJumpTo(source)) { if (!validForwardParents.contains(source)) { // source from outside the block. This likely means that we've actually left the block. // eg // if (foo) goto Z // .... // return 1; // label: // statement <-- here. // ... // Z // // (although it might mean some horrid duffs device style compiler output). // TODO: CheckForDuff As below. //if (statementIsReachableFrom(statementCurrent, ifStatement)) return false; // We want to avoid breaking up a try block. if (statementPrev != statementStart && !SetUtil.equals(Functional.filterSet(statementPrev.getBlockIdentifiers(), tryBlockFilter), startTryBlocks)) { return false; } Op03SimpleStatement newJump = new Op03SimpleStatement(ifStatement.getBlockIdentifiers(), new GotoStatement(), statementCurrent.getIndex().justBefore()); if (statementCurrent != ifStatement.getTargets().get(0)) { Op03SimpleStatement oldTarget = ifStatement.getTargets().get(1); newJump.addTarget(oldTarget); newJump.addSource(ifStatement); ifStatement.replaceTarget(oldTarget, newJump); oldTarget.replaceSource(ifStatement, newJump); statements.add(idxCurrent, newJump); return true; } } } } } validForwardParents.add(statementCurrent); ifBranch.add(statementCurrent); JumpType jumpType = statementCurrent.getJumpType(); if (jumpType.isUnknown() && !ignoreTheseJumps.contains(statementCurrent)) { // Todo : Currently we can only cope with hitting // the last jump as an unknown jump. We ought to be able to rewrite // i.e. if we have /* if (list != null) goto lbl6; System.out.println("A"); if (set != null) goto lbl8; <<----- System.out.println("B"); goto lbl8; lbl6: ELSE BLOCK lbl8: return true; */ // this is a problem, because the highlighted statement will cause us to abandon processing. if (idxCurrent == idxTaken - 1) { Statement mGotoStatement = statementCurrent.getStatement(); if (!(mGotoStatement.getClass() == GotoStatement.class)) return false; GotoStatement gotoStatement = (GotoStatement) mGotoStatement; // It's unconditional, and it's a forward jump. maybeElseEnd = statementCurrent.getTargets().get(0); maybeElseEndIdx = statements.indexOf(maybeElseEnd); if (maybeElseEnd.getIndex().compareTo(takenTarget.getIndex()) <= 0) { return false; } leaveIfBranchHolder = statementCurrent; leaveIfBranchGoto = gotoStatement; maybeSimpleIfElse = true; } else { if (stmtLastBlockRewrite == null) { Op03SimpleStatement tgtContainer = statementCurrent.getTargets().get(0); if (tgtContainer == takenTarget) { // We can ignore this statement in future passes. idxCurrent++; continue; } return false; } // We can try to rewrite this block to have an indirect jump via the end of the block, // if that's appropriate. List<Op03SimpleStatement> targets = statementCurrent.getTargets(); Op03SimpleStatement eventualTarget = stmtLastBlockRewrite.getTargets().get(0); boolean found = false; for (int x = 0; x < targets.size(); ++x) { Op03SimpleStatement target = targets.get(x); if (target == eventualTarget && target != stmtLastBlockRewrite) { // Equivalent to // statementCurrent.replaceTarget(eventualTarget, stmtLastBlockRewrite); targets.set(x, stmtLastBlockRewrite); stmtLastBlockRewrite.addSource(statementCurrent); // Todo : I don't believe the latter branch will EVER happen. if (eventualTarget.getSources().contains(stmtLastBlockRewrite)) { eventualTarget.removeSource(statementCurrent); } else { eventualTarget.replaceSource(statementCurrent, stmtLastBlockRewrite); } found = true; } } return found; } } idxCurrent++; statementPrev = statementCurrent; } while (idxCurrent != idxEnd); // We've reached the "other" branch of the conditional. // If maybeSimpleIfElse is set, then there was a final jump to if (maybeSimpleIfElse) { // If there is a NO JUMP path between takenTarget and maybeElseEnd, then that's the ELSE block elseBranch = ListFactory.newList(); idxCurrent = idxTaken; idxEnd = maybeElseEndIdx; do { Op03SimpleStatement statementCurrent = statements.get(idxCurrent); elseBranch.add(statementCurrent); JumpType jumpType = statementCurrent.getJumpType(); if (jumpType.isUnknown()) { /* We allow ONE unconditional forward jump, to maybeElseEnd. If we find this, we have * a simple if /else/ block, which we can rewrite as * if (a) { .. .goto X } else { .... goto X } --> * if (a) { ... } else { ....} ; goto X */ Statement mGotoStatement = statementCurrent.getStatement(); if (!(mGotoStatement.getClass() == GotoStatement.class)) return false; // It's unconditional, and it's a forward jump. if (statementCurrent.getTargets().get(0) == maybeElseEnd) { idxEnd = idxCurrent; idxCurrent--; // We can do this aggressively, as it doesn't break the graph. leaveIfBranchHolder.replaceTarget(maybeElseEnd, statementCurrent); statementCurrent.addSource(leaveIfBranchHolder); maybeElseEnd.removeSource(leaveIfBranchHolder); elseBranch.remove(statementCurrent); // eww. takenAction = true; } else { return false; } } idxCurrent++; } while (idxCurrent != idxEnd); } Op03SimpleStatement realEnd = statements.get(idxEnd); Set<BlockIdentifier> blocksAtEnd = realEnd.getBlockIdentifiers(); // If we've changed the blocks we're in, that means we've jumped into a block. // The only way we can cope with this, is if we're jumping into a block which we subsequently change // to be an anonymous escape. mismatchedBlocks : if (!(blocksAtStart.equals(blocksAtEnd))) { /* * A 'normal' case where we might jump into a different blockset - if we've jumped over * the body of a switch block - if this is the case, the only difference will be one case statement, * and we'll jump into a case statement - the statement LINEARLY BEFORE that will be in the missing block. */ if (blocksAtStart.size() == blocksAtEnd.size()+1) { List<BlockIdentifier> change = SetUtil.differenceAtakeBtoList(blocksAtStart, blocksAtEnd); // size == 1 already verified, but... if (change.size() == 1 && change.get(0).getBlockType() == BlockType.CASE) { if (takenTarget.getStatement() instanceof CaseStatement) { // We need to check if the statement LINEARLY preceeding this is in the block we've left. if (stmtLastBlock.getBlockIdentifiers().contains(change.get(0))) { break mismatchedBlocks; } } // Ok, but what about SwitchTest38? Jump if still inside the case, but the last item in the // else block is the end of the case. Op03SimpleStatement beforeRealEnd = statements.get(idxEnd-1); if (blocksAtStart.equals(beforeRealEnd.getBlockIdentifiers())) { break mismatchedBlocks; } } } /* * Handle some ugly kinds of conditionals that dex2jar generates. (Comments at function). */ if (detectAndRemarkJumpIntoOther(blocksAtStart, blocksAtEnd, realEnd, ifStatement)) break mismatchedBlocks; return takenAction; } // It's an if statement / simple if/else, for sure. Can we replace it with a ternary? DiscoveredTernary ternary = testForTernary(ifBranch, elseBranch, leaveIfBranchHolder); if (ternary != null) { // We can ditch this whole thing for a ternary expression. for (Op03SimpleStatement statement : ifBranch) statement.nopOut(); for (Op03SimpleStatement statement : elseBranch) statement.nopOut(); // todo : do I need to do a more complex merge? ifStatement.forceSSAIdentifiers(leaveIfBranchHolder.getSSAIdentifiers()); // We need to be careful we don't replace x ? 1 : 0 with x here unless we're ABSOLUTELY // sure that the final expression type is boolean..... // this may come back to bite. (at which point we can inject a (? 1 : 0) ternary... ConditionalExpression conditionalExpression = innerIfStatement.getCondition().getNegated().simplify(); Expression rhs = ternary.isPointlessBoolean() ? conditionalExpression : new TernaryExpression( conditionalExpression, ternary.e1, ternary.e2); ifStatement.replaceStatement( new AssignmentSimple( ternary.lValue, rhs ) ); // Reduce the ternary lValue's created location count, if we can. if (ternary.lValue instanceof StackSSALabel) { StackSSALabel stackSSALabel = (StackSSALabel) ternary.lValue; StackEntry stackEntry = stackSSALabel.getStackEntry(); stackEntry.decSourceCount(); } // If statement now should have only one target. List<Op03SimpleStatement> tmp = ListFactory.uniqueList(ifStatement.getTargets()); ifStatement.getTargets().clear(); ifStatement.getTargets().addAll(tmp); if (ifStatement.getTargets().size() != 1) { throw new ConfusedCFRException("If statement should only have one target after dedup"); } Op03SimpleStatement joinStatement = ifStatement.getTargets().get(0); tmp = ListFactory.uniqueList(joinStatement.getSources()); joinStatement.getSources().clear(); joinStatement.getSources().addAll(tmp); /* * And now we need to reapply LValue condensing in the region! :( */ LValueProp.condenseLValues(statements); return true; } BlockIdentifier elseBlockLabel = null; if (maybeSimpleIfElse && elseBranch.isEmpty()) { maybeSimpleIfElse = false; } boolean flipBlocks = false; doneElse : if (maybeSimpleIfElse) { elseBlockLabel = blockIdentifierFactory.getNextBlockIdentifier(BlockType.SIMPLE_IF_ELSE); Misc.markWholeBlock(elseBranch, elseBlockLabel); /* * Can the else block be escaped? If not, we have the opportunity to push content from the if block out to after.. * * i.e. * * if (x) { * a * b * } else { * c * return * } * * both a&b could be pushed after the block! */ // First - detect if the "if" block-to-be has any foreign sources. Set<Op03SimpleStatement> allIfSources = Misc.collectAllSources(ifBranch); allIfSources.removeAll(ifBranch); allIfSources.remove(ifStatement); if (allIfSources.isEmpty()) break doneElse; Op03SimpleStatement elseStart = elseBranch.get(0); Pair<Set<Op03SimpleStatement>, Set<Op03SimpleStatement>> reachinfo = Misc.GraphVisitorBlockReachable.getBlockReachableAndExits(elseStart, elseBlockLabel); Set<Op03SimpleStatement> reachableElse = reachinfo.getFirst(); if (!(reachableElse.size() == elseBranch.size() && reachinfo.getSecond().isEmpty())) break doneElse; // Ok - invert the test, move the if statements after, relabel. innerIfStatement.negateCondition(); List<Op03SimpleStatement> targets = ifStatement.getTargets(); Op03SimpleStatement tgt0 = targets.get(0); Op03SimpleStatement tgt1 = targets.get(1); targets.clear(); targets.add(tgt1); targets.add(tgt0); leaveIfBranchGoto = null; List<Op03SimpleStatement> oldIfBranch = ifBranch; ifBranch = elseBranch; Collections.sort(ifBranch, new CompareByIndex()); Op03SimpleStatement last = ifBranch.get(ifBranch.size()-1); InstrIndex fromHere = last.getIndex().justAfter(); Cleaner.sortAndRenumberFromInPlace(oldIfBranch, fromHere); ignoreLocally.removeAll(oldIfBranch); flipBlocks = true; elseBlockLabel.setBlockType(BlockType.SIMPLE_IF_TAKEN); } BlockIdentifier ifBlockLabel = blockIdentifierFactory.getNextBlockIdentifier(BlockType.SIMPLE_IF_TAKEN); if (flipBlocks) { ifBlockLabel = elseBlockLabel; elseBlockLabel = null; } else { // If there are any prior jumps INTO this block, we can't mark it as this will cause anonymous blocks // to be misclassified. if (!maybeSimpleIfElse) { // Should not need this condition - but we may have already done some work above :( for (Op03SimpleStatement stm : ifBranch) { for (Op03SimpleStatement source : stm.getSources()) { if (source.getIndex().isBackJumpFrom(ifStatement)) { // Nope - we'd end up creating a bad structure. return false; } } } } Misc.markWholeBlock(ifBranch, ifBlockLabel); } if (leaveIfBranchGoto != null) leaveIfBranchGoto.setJumpType(JumpType.GOTO_OUT_OF_IF); innerIfStatement.setJumpType(JumpType.GOTO_OUT_OF_IF); innerIfStatement.setKnownBlocks(ifBlockLabel, elseBlockLabel); ignoreTheseJumps.addAll(ignoreLocally); if (flipBlocks) { Cleaner.sortAndRenumberInPlace(statements); } return true; } private static DiscoveredTernary testForTernary(List<Op03SimpleStatement> ifBranch, List<Op03SimpleStatement> elseBranch, Op03SimpleStatement leaveIfBranch) { if (ifBranch == null || elseBranch == null) return null; if (leaveIfBranch == null) return null; TypeFilter<Nop> notNops = new TypeFilter<Nop>(Nop.class, false); ifBranch = Functional.filter(ifBranch, notNops); switch (ifBranch.size()) { case 1: break; case 2: if (ifBranch.get(1) != leaveIfBranch) { return null; } break; default: return null; } if (ifBranch.get(0).getSources().size() != 1) return null; elseBranch = Functional.filter(elseBranch, notNops); if (elseBranch.size() != 1) { return null; } if (elseBranch.get(0).getSources().size() != 1) return null; Op03SimpleStatement s1 = ifBranch.get(0); Op03SimpleStatement s2 = elseBranch.get(0); if (s2.getSources().size() != 1) return null; LValue l1 = s1.getStatement().getCreatedLValue(); LValue l2 = s2.getStatement().getCreatedLValue(); if (l1 == null || l2 == null) { return null; } if (!l2.equals(l1)) { return null; } return new DiscoveredTernary(l1, s1.getStatement().getRValue(), s2.getStatement().getRValue()); } private static class DiscoveredTernary { LValue lValue; Expression e1; Expression e2; private DiscoveredTernary(LValue lValue, Expression e1, Expression e2) { this.lValue = lValue; this.e1 = e1; this.e2 = e2; } private static Troolean isOneOrZeroLiteral(Expression e) { if (!(e instanceof Literal)) return Troolean.NEITHER; TypedLiteral typedLiteral = ((Literal) e).getValue(); Object value = typedLiteral.getValue(); if (!(value instanceof Integer)) return Troolean.NEITHER; int iValue = (Integer) value; if (iValue == 1) return Troolean.TRUE; if (iValue == 0) return Troolean.FALSE; return Troolean.NEITHER; } private boolean isPointlessBoolean() { if (!(e1.getInferredJavaType().getRawType() == RawJavaType.BOOLEAN && e2.getInferredJavaType().getRawType() == RawJavaType.BOOLEAN)) return false; if (isOneOrZeroLiteral(e1) != Troolean.TRUE) return false; if (isOneOrZeroLiteral(e2) != Troolean.FALSE) return false; return true; } } }