/* * Fernflower - The Analytical Java Decompiler * http://www.reversed-java.com * * (C) 2008 - 2010, Stiver * * This software is NEITHER public domain NOR free software * as per GNU License. See license.txt for more details. * * This software is distributed WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. */ package de.fernflower.modules.decompiler; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import de.fernflower.code.CodeConstants; import de.fernflower.code.ConstantsUtil; import de.fernflower.code.Instruction; import de.fernflower.code.InstructionSequence; import de.fernflower.code.SimpleInstructionSequence; import de.fernflower.code.cfg.BasicBlock; import de.fernflower.code.cfg.ControlFlowGraph; import de.fernflower.code.cfg.ExceptionRangeCFG; import de.fernflower.main.DecompilerContext; import de.fernflower.main.collectors.CounterContainer; import de.fernflower.main.extern.IFernflowerPreferences; import de.fernflower.modules.code.DeadCodeHelper; import de.fernflower.modules.decompiler.exps.AssignmentExprent; import de.fernflower.modules.decompiler.exps.ExitExprent; import de.fernflower.modules.decompiler.exps.Exprent; import de.fernflower.modules.decompiler.exps.VarExprent; import de.fernflower.modules.decompiler.sforms.DirectGraph; import de.fernflower.modules.decompiler.sforms.DirectNode; import de.fernflower.modules.decompiler.sforms.FlattenStatementsHelper; import de.fernflower.modules.decompiler.sforms.SSAConstructorSparseEx; import de.fernflower.modules.decompiler.stats.BasicBlockStatement; import de.fernflower.modules.decompiler.stats.CatchAllStatement; import de.fernflower.modules.decompiler.stats.RootStatement; import de.fernflower.modules.decompiler.stats.Statement; import de.fernflower.modules.decompiler.vars.VarProcessor; import de.fernflower.modules.decompiler.vars.VarVersionPaar; import de.fernflower.struct.StructMethod; import de.fernflower.struct.gen.VarType; import de.fernflower.util.InterpreterUtil; public class FinallyProcessor { private HashMap finallyBlockIDs = new HashMap(); private HashMap catchallBlockIDs = new HashMap(); private VarProcessor varprocessor; public FinallyProcessor(VarProcessor varprocessor) { this.varprocessor = varprocessor; } public boolean iterateGraph(StructMethod mt, RootStatement root, ControlFlowGraph graph) { // return processStatement(mt, root, graph, root); return processStatementEx(mt, root, graph); } private boolean processStatementEx(StructMethod mt, RootStatement root, ControlFlowGraph graph) { int bytecode_version = mt.getClassStruct().getBytecodeVersion(); LinkedList stack = new LinkedList(); stack.add(root); while(!stack.isEmpty()) { Statement stat = stack.removeLast(); Statement parent = stat.getParent(); if(parent != null && parent.type == Statement.TYPE_CATCHALL && stat == parent.getFirst() && !parent.isCopied()) { CatchAllStatement fin = (CatchAllStatement)parent; BasicBlock head = fin.getBasichead().getBlock(); BasicBlock handler = fin.getHandler().getBasichead().getBlock(); if(catchallBlockIDs.containsKey(handler.id)) { ; // do nothing }else if(finallyBlockIDs.containsKey(handler.id)) { fin.setFinally(true); Integer var = finallyBlockIDs.get(handler.id); fin.setMonitor(var==null?null:new VarExprent(var.intValue(), VarType.VARTYPE_INT, varprocessor)); } else { Object[] inf = getFinallyInformation(mt, root, fin); if(inf == null) { // inconsistent finally catchallBlockIDs.put(handler.id, null); } else { if(DecompilerContext.getOption(IFernflowerPreferences.FINALLY_DEINLINE) && verifyFinallyEx(graph, fin, inf)) { finallyBlockIDs.put(handler.id, null); } else { int varindex = DecompilerContext.getCountercontainer().getCounterAndIncrement(CounterContainer.VAR_COUNTER); insertSemaphore(graph, getAllBasicBlocks(fin.getFirst()), head, handler, varindex, inf, bytecode_version); finallyBlockIDs.put(handler.id, varindex); } DeadCodeHelper.removeDeadBlocks(graph); // e.g. multiple return blocks after a nested finally DeadCodeHelper.removeEmptyBlocks(graph); DeadCodeHelper.mergeBasicBlocks(graph); } return true; } } stack.addAll(stat.getStats()); } return false; } // private boolean processStatement(StructMethod mt, RootStatement root, ControlFlowGraph graph, Statement stat) { // // boolean res = false; // // for(int i=stat.getStats().size()-1;i>=0;i--) { // if(processStatement(mt, root, graph, stat.getStats().get(i))) { // return true; // } // } // // // if(stat.type == Statement.TYPE_CATCHALL && !stat.isCopied()) { // // CatchAllStatement fin = (CatchAllStatement)stat; // BasicBlock head = fin.getBasichead().getBlock(); // BasicBlock handler = fin.getHandler().getBasichead().getBlock(); // // if(catchallBlockIDs.containsKey(handler.id)) { // ; // do nothing // }else if(finallyBlockIDs.containsKey(handler.id)) { // // fin.setFinally(true); // // Integer var = finallyBlockIDs.get(handler.id); // fin.setMonitor(var==null?null:new VarExprent(var.intValue(), VarType.VARTYPE_INT, varprocessor)); // // } else { // // Object[] inf = getFinallyInformation(mt, root, fin); // // if(inf == null) { // inconsistent finally // catchallBlockIDs.put(handler.id, null); // } else { // // if(DecompilerContext.getOption(IFernflowerPreferences.FINALLY_DEINLINE) && verifyFinallyEx(graph, fin, inf)) { // finallyBlockIDs.put(handler.id, null); // } else { // // int varindex = DecompilerContext.getCountercontainer().getCounterAndIncrement(CounterContainer.VAR_COUNTER); // insertSemaphore(graph, getAllBasicBlocks(fin.getFirst()), head, handler, varindex, inf); // // finallyBlockIDs.put(handler.id, varindex); // } // // DeadCodeHelper.removeEmptyBlocks(graph); // DeadCodeHelper.mergeBasicBlocks(graph); // } // // res = true; // } // } // // return res; // } private Object[] getFinallyInformation(StructMethod mt, RootStatement root, CatchAllStatement fstat) { HashMap mapLast = new HashMap(); BasicBlockStatement firstBlockStatement = fstat.getHandler().getBasichead(); BasicBlock firstBasicBlock = firstBlockStatement.getBlock(); Instruction instrFirst = firstBasicBlock.getInstruction(0); int firstcode = 0; switch(instrFirst.opcode) { case CodeConstants.opc_pop: firstcode = 1; break; case CodeConstants.opc_astore: firstcode = 2; } ExprProcessor proc = new ExprProcessor(); proc.processStatement(root, mt.getClassStruct().getPool()); SSAConstructorSparseEx ssa = new SSAConstructorSparseEx(); ssa.splitVariables(root, mt); List lstExprents = firstBlockStatement.getExprents(); VarVersionPaar varpaar = new VarVersionPaar((VarExprent)((AssignmentExprent)lstExprents.get(firstcode==2?1:0)).getLeft()); FlattenStatementsHelper flatthelper = new FlattenStatementsHelper(); DirectGraph dgraph = flatthelper.buildDirectGraph(root); LinkedList stack = new LinkedList(); stack.add(dgraph.first); HashSet setVisited = new HashSet(); while(!stack.isEmpty()) { DirectNode node = stack.removeFirst(); if(setVisited.contains(node)) { continue; } setVisited.add(node); BasicBlockStatement blockStatement = null; if(node.block != null) { blockStatement = node.block; } else if(node.preds.size() == 1) { blockStatement = node.preds.get(0).block; } boolean isTrueExit = true; if(firstcode != 1) { isTrueExit = false; for(int i=0;i lst = exprent.getAllExprents(); lst.add(exprent); boolean found = false; for(Exprent expr : lst) { if(expr.type == Exprent.EXPRENT_VAR && new VarVersionPaar((VarExprent)expr).equals(varpaar)) { found = true; break; } } if(found) { found = false; if(exprent.type == Exprent.EXPRENT_EXIT) { ExitExprent exexpr = (ExitExprent)exprent; if(exexpr.getExittype() == ExitExprent.EXIT_THROW && exexpr.getValue().type == Exprent.EXPRENT_VAR) { found = true; } } if(!found) { return null; } else { isTrueExit = true; } } } else if(firstcode == 2) { // search for a load instruction if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { AssignmentExprent assexpr = (AssignmentExprent)exprent; if(assexpr.getRight().type == Exprent.EXPRENT_VAR && new VarVersionPaar((VarExprent)assexpr.getRight()).equals(varpaar)) { Exprent next = null; if(i == node.exprents.size()-1) { if(node.succs.size() == 1) { DirectNode nd = node.succs.get(0); if(!nd.exprents.isEmpty()) { next = nd.exprents.get(0); } } } else { next = node.exprents.get(i+1); } boolean found = false; if(next != null && next.type == Exprent.EXPRENT_EXIT) { ExitExprent exexpr = (ExitExprent)next; if(exexpr.getExittype() == ExitExprent.EXIT_THROW && exexpr.getValue().type == Exprent.EXPRENT_VAR && assexpr.getLeft().equals(exexpr.getValue())) { found = true; } } if(!found) { return null; } else { isTrueExit = true; } } } } } } // find finally exits if(blockStatement != null && blockStatement.getBlock() != null) { Statement handler = fstat.getHandler(); for(StatEdge edge : blockStatement.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL)) { if(edge.getType() != StatEdge.TYPE_REGULAR && handler.containsStatement(blockStatement) && !handler.containsStatement(edge.getDestination())) { Boolean existingFlag = mapLast.get(blockStatement.getBlock()); // note: the dummy node is also processed! if(existingFlag == null || !existingFlag) { mapLast.put(blockStatement.getBlock(), isTrueExit); break; } } } } stack.addAll(node.succs); } // empty finally block? if(fstat.getHandler().type == Statement.TYPE_BASICBLOCK) { boolean isEmpty = false; boolean isFirstLast = mapLast.containsKey(firstBasicBlock); InstructionSequence seq = firstBasicBlock.getSeq(); switch(firstcode) { case 0: isEmpty = isFirstLast && seq.length() == 1; break; case 1: isEmpty = seq.length() == 1; break; case 2: isEmpty = isFirstLast?seq.length()==3:seq.length()==1; } if(isEmpty) { firstcode = 3; } } return new Object[] {firstcode, mapLast}; } private void insertSemaphore(ControlFlowGraph graph, HashSet setTry, BasicBlock head, BasicBlock handler, int var, Object[] information, int bytecode_version) { HashSet setCopy = new HashSet(setTry); int finallytype = (Integer)information[0]; HashMap mapLast = (HashMap)information[1]; // first and last statements removeExceptionInstructionsEx(handler, 1, finallytype); for(Entry entry : mapLast.entrySet()) { BasicBlock last = entry.getKey(); if(entry.getValue()) { removeExceptionInstructionsEx(last, 2, finallytype); graph.getFinallyExits().add(last); } } // disable semaphore at statement exit points for(BasicBlock block: setTry) { List lstSucc = block.getSuccs(); for(BasicBlock dest : lstSucc) { // break out if(!setCopy.contains(dest) && dest != graph.getLast()) { // disable semaphore SimpleInstructionSequence seq = new SimpleInstructionSequence(); seq.addInstruction(ConstantsUtil.getInstructionInstance(CodeConstants.opc_bipush, false, CodeConstants.GROUP_GENERAL, bytecode_version, new int[]{0}) , -1); seq.addInstruction(ConstantsUtil.getInstructionInstance(CodeConstants.opc_istore, false, CodeConstants.GROUP_GENERAL, bytecode_version, new int[]{var}) , -1); // build a separate block BasicBlock newblock = new BasicBlock(++graph.last_id); newblock.setSeq(seq); // insert between block and dest block.replaceSuccessor(dest, newblock); newblock.addSuccessor(dest); setCopy.add(newblock); graph.getBlocks().addWithKey(newblock, newblock.id); // exception ranges // FIXME: special case synchronized // copy exception edges and extend protected ranges for(int j=0;j(newheadinit.getSuccExceptions())) { ExceptionRangeCFG range = graph.getExceptionRange(hd, newheadinit); if(setCopy.containsAll(range.getProtectedRange())) { newheadinit.removeSuccessorException(hd); range.getProtectedRange().remove(newheadinit); } } return; } private void insertBlockBefore(ControlFlowGraph graph, BasicBlock oldblock, BasicBlock newblock) { List lstTemp = new ArrayList(); lstTemp.addAll(oldblock.getPreds()); lstTemp.addAll(oldblock.getPredExceptions()); // replace predecessors for(BasicBlock pred: lstTemp) { pred.replaceSuccessor(oldblock, newblock); } // copy exception edges and extend protected ranges for(BasicBlock hd: oldblock.getSuccExceptions()) { newblock.addSuccessorException(hd); ExceptionRangeCFG range = graph.getExceptionRange(hd, oldblock); range.getProtectedRange().add(newblock); } // replace handler for(ExceptionRangeCFG range: graph.getExceptions()) { if(range.getHandler() == oldblock) { range.setHandler(newblock); } } newblock.addSuccessor(oldblock); graph.getBlocks().addWithKey(newblock, newblock.id); if(graph.getFirst() == oldblock) { graph.setFirst(newblock); } } private HashSet getAllBasicBlocks(Statement stat) { List lst = new LinkedList(); lst.add(stat); int index = 0; do { Statement st = lst.get(index); if(st.type == Statement.TYPE_BASICBLOCK) { index++; } else { lst.addAll(st.getStats()); lst.remove(index); } } while(index res = new HashSet(); for(Statement st: lst) { res.add(((BasicBlockStatement)st).getBlock()); } return res; } private boolean verifyFinallyEx(ControlFlowGraph graph, CatchAllStatement fstat, Object[] information) { HashSet tryBlocks = getAllBasicBlocks(fstat.getFirst()); HashSet catchBlocks = getAllBasicBlocks(fstat.getHandler()); int finallytype = (Integer)information[0]; HashMap mapLast = (HashMap)information[1]; BasicBlock first = fstat.getHandler().getBasichead().getBlock(); boolean skippedFirst = false; if(finallytype == 3) { // empty finally removeExceptionInstructionsEx(first, 3, finallytype); if(mapLast.containsKey(first)) { graph.getFinallyExits().add(first); } return true; } else { if(first.getSeq().length() == 1 && finallytype>0) { BasicBlock firstsuc = first.getSuccs().get(0); if(catchBlocks.contains(firstsuc)) { first = firstsuc; skippedFirst = true; } } } // identify start blocks HashSet startBlocks = new HashSet(); for(BasicBlock block: tryBlocks) { startBlocks.addAll(block.getSuccs()); } // throw in the try body will point directly to the dummy exit // so remove dummy exit startBlocks.remove(graph.getLast()); startBlocks.removeAll(tryBlocks); List lstAreas = new ArrayList(); for(BasicBlock start: startBlocks) { Object[] arr = compareSubgraphsEx(graph, start, catchBlocks, first, finallytype, mapLast, skippedFirst); if(arr == null) { return false; } lstAreas.add(new Object[] {start, arr[0], arr[1]}); } // try { // DotExporter.toDotFile(graph, new File("c:\\Temp\\fern5.dot"), true); // } catch(Exception ex){ex.printStackTrace();} // delete areas for(Object[] area: lstAreas) { deleteArea(graph, area); } // try { // DotExporter.toDotFile(graph, new File("c:\\Temp\\fern5.dot"), true); // } catch(Exception ex){ex.printStackTrace();} // INFO: empty basic blocks may remain in the graph! for(Entry entry : mapLast.entrySet()) { BasicBlock last = entry.getKey(); if(entry.getValue()) { removeExceptionInstructionsEx(last, 2, finallytype); graph.getFinallyExits().add(last); } } removeExceptionInstructionsEx(fstat.getHandler().getBasichead().getBlock(), 1, finallytype); return true; } private Object[] compareSubgraphsEx(ControlFlowGraph graph, BasicBlock startSample, HashSet catchBlocks, BasicBlock startCatch, int finallytype, HashMap mapLast, boolean skippedFirst) { class BlockStackEntry { public BasicBlock blockCatch; public BasicBlock blockSample; // TODO: correct handling (merging) of multiple paths public List lstStoreVars; public BlockStackEntry(BasicBlock blockCatch, BasicBlock blockSample, List lstStoreVars) { this.blockCatch = blockCatch; this.blockSample = blockSample; this.lstStoreVars = new ArrayList(lstStoreVars); } } List stack = new LinkedList(); HashSet setSample = new HashSet(); HashMap mapNext = new HashMap(); stack.add(new BlockStackEntry(startCatch, startSample, new ArrayList())); while(!stack.isEmpty()) { BlockStackEntry entry = stack.remove(0); BasicBlock blockCatch = entry.blockCatch; BasicBlock blockSample = entry.blockSample; boolean isFirstBlock = !skippedFirst && blockCatch == startCatch; boolean isLastBlock = mapLast.containsKey(blockCatch); boolean isTrueLastBlock = isLastBlock && mapLast.get(blockCatch); if(!compareBasicBlocksEx(graph, blockCatch, blockSample, (isFirstBlock?1:0) | (isTrueLastBlock?2:0), finallytype, entry.lstStoreVars)) { return null; } if(blockSample.getSuccs().size() != blockCatch.getSuccs().size()) { return null; } setSample.add(blockSample); // direct successors for(int i=0;i lst = entry.lstStoreVars; if(sucCatch.getSeq().length() > 0 && sucSample.getSeq().length() > 0) { Instruction instrCatch = sucCatch.getSeq().getInstr(0); Instruction instrSample = sucSample.getSeq().getInstr(0); if(instrCatch.opcode == CodeConstants.opc_astore && instrSample.opcode == CodeConstants.opc_astore) { lst = new ArrayList(lst); lst.add(new int[]{instrCatch.getOperand(0), instrSample.getOperand(0)}); } } stack.add(new BlockStackEntry(sucCatch, sucSample, lst)); } } else { return null; } } } else { return null; } } if(isLastBlock) { HashSet setSuccs = new HashSet(blockSample.getSuccs()); setSuccs.removeAll(setSample); for(BlockStackEntry stackent : stack) { setSuccs.remove(stackent.blockSample); } for(BasicBlock succ : setSuccs) { if(graph.getLast() != succ) { // FIXME: why? mapNext.put(blockSample.id+"#"+succ.id, new BasicBlock[] {blockSample, succ, isTrueLastBlock?succ:null}); } } } } return new Object[]{setSample, getUniqueNext(graph, new HashSet(mapNext.values()))}; } private BasicBlock getUniqueNext(ControlFlowGraph graph, HashSet setNext) { // precondition: there is at most one true exit path in a finally statement BasicBlock next = null; boolean multiple = false; for(BasicBlock[] arr : setNext) { if(arr[2] != null) { next = arr[1]; multiple = false; break; } else { if(next == null) { next = arr[1]; } else if(next != arr[1]) { multiple = true; } if(arr[1].getPreds().size() == 1) { next = arr[1]; } } } if(multiple) { // TODO: generic solution for(BasicBlock[] arr : setNext) { BasicBlock block = arr[1]; if(block != next) { if(InterpreterUtil.equalSets(next.getSuccs(), block.getSuccs())) { InstructionSequence seqNext = next.getSeq(); InstructionSequence seqBlock = block.getSeq(); if(seqNext.length() == seqBlock.length()) { for(int i=0;i lstStoreVars) { InstructionSequence seqPattern = pattern.getSeq(); InstructionSequence seqSample = sample.getSeq(); if(type!=0) { seqPattern = seqPattern.clone(); if((type & 1) > 0) { // first if(finallytype > 0) { seqPattern.removeInstruction(0); } } if((type & 2) > 0) { // last if(finallytype == 0 || finallytype == 2) { seqPattern.removeInstruction(seqPattern.length()-1); } if(finallytype == 2) { seqPattern.removeInstruction(seqPattern.length()-1); } } } if(seqPattern.length() > seqSample.length()) { return false; } for(int i=0;i=seqPattern.length();i--) { seq.addInstruction(0, seqSample.getInstr(i), -1); seqSample.removeInstruction(i); } BasicBlock newblock = new BasicBlock(++graph.last_id); newblock.setSeq(seq); List lstTemp = new ArrayList(); lstTemp.addAll(sample.getSuccs()); // move successors for(BasicBlock suc: lstTemp) { sample.removeSuccessor(suc); newblock.addSuccessor(suc); } sample.addSuccessor(newblock); graph.getBlocks().addWithKey(newblock, newblock.id); HashSet setFinallyExits = graph.getFinallyExits(); if(setFinallyExits.contains(sample)) { setFinallyExits.remove(sample); setFinallyExits.add(newblock); } // copy exception edges and extend protected ranges for(int j=0;j lstStoreVars) { if(first.opcode != second.opcode || first.wide != second.wide || first.operandsCount() != second.operandsCount()) { return false; } if(first.group != CodeConstants.GROUP_JUMP && first.getOperands() != null) { // FIXME: switch comparison for(int i=0;i setCommonExceptionHandlers = new HashSet(next.getSuccExceptions()); for(BasicBlock pred : start.getPreds()) { setCommonExceptionHandlers.retainAll(pred.getSuccExceptions()); } boolean is_outside_range = false; HashSet setPredecessors = new HashSet(start.getPreds()); // replace start with next for(BasicBlock pred: setPredecessors) { pred.replaceSuccessor(start, next); } HashSet setBlocks = (HashSet)area[1]; HashSet setCommonRemovedExceptionRanges = null; // remove all the blocks inbetween for(BasicBlock block: setBlocks) { // artificial basic blocks (those resulted from splitting) // can belong to more than one area if(graph.getBlocks().containsKey(block.id)) { if(!block.getSuccExceptions().containsAll(setCommonExceptionHandlers)) { is_outside_range = true; } HashSet setRemovedExceptionRanges = new HashSet(); for(BasicBlock handler : block.getSuccExceptions()) { setRemovedExceptionRanges.add(graph.getExceptionRange(handler, block)); } if(setCommonRemovedExceptionRanges == null) { setCommonRemovedExceptionRanges = setRemovedExceptionRanges; } else { setCommonRemovedExceptionRanges.retainAll(setRemovedExceptionRanges); } // shift extern edges on splitted blocks if(block.getSeq().isEmpty() && block.getSuccs().size() == 1) { BasicBlock succs = block.getSuccs().get(0); for(BasicBlock pred : new ArrayList(block.getPreds())) { if(!setBlocks.contains(pred)) { pred.replaceSuccessor(block, succs); } } if(graph.getFirst() == block) { graph.setFirst(succs); } } graph.removeBlock(block); } } if(is_outside_range) { // new empty block BasicBlock emptyblock = new BasicBlock(++graph.last_id); emptyblock.setSeq(new SimpleInstructionSequence()); graph.getBlocks().addWithKey(emptyblock, emptyblock.id); // add to ranges if necessary if(setCommonRemovedExceptionRanges != null) { for(ExceptionRangeCFG range : setCommonRemovedExceptionRanges) { emptyblock.addSuccessorException(range.getHandler()); range.getProtectedRange().add(emptyblock); } } // insert between predecessors and next emptyblock.addSuccessor(next); for(BasicBlock pred: setPredecessors) { pred.replaceSuccessor(next, emptyblock); } } } private void removeExceptionInstructionsEx(BasicBlock block, int blocktype, int finallytype) { InstructionSequence seq = block.getSeq(); if(finallytype == 3) { // empty finally handler for(int i=seq.length()-1;i>=0;i--) { seq.removeInstruction(i); } } else { if((blocktype & 1) > 0) { // first if(finallytype == 2 || finallytype == 1) { // astore or pop seq.removeInstruction(0); } } if((blocktype & 2) > 0) { // last if(finallytype == 2 || finallytype == 0) { seq.removeInstruction(seq.length()-1); } if(finallytype == 2) { // astore seq.removeInstruction(seq.length()-1); } } } } }