/* * Bytecode Analysis Framework * Copyright (C) 2003,2004 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.bcel.Constants; import org.apache.bcel.classfile.ClassParser; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.classfile.Method; import org.apache.bcel.generic.ACONST_NULL; import org.apache.bcel.generic.ALOAD; import org.apache.bcel.generic.BranchInstruction; import org.apache.bcel.generic.ClassGen; import org.apache.bcel.generic.CodeExceptionGen; import org.apache.bcel.generic.ConstantPoolGen; import org.apache.bcel.generic.ExceptionThrower; import org.apache.bcel.generic.GETFIELD; import org.apache.bcel.generic.GETSTATIC; import org.apache.bcel.generic.GOTO; import org.apache.bcel.generic.ICONST; import org.apache.bcel.generic.IFNONNULL; import org.apache.bcel.generic.IFNULL; import org.apache.bcel.generic.IF_ACMPEQ; import org.apache.bcel.generic.IF_ACMPNE; import org.apache.bcel.generic.INSTANCEOF; import org.apache.bcel.generic.INVOKESTATIC; import org.apache.bcel.generic.IfInstruction; import org.apache.bcel.generic.Instruction; import org.apache.bcel.generic.InstructionHandle; import org.apache.bcel.generic.InstructionList; import org.apache.bcel.generic.InstructionTargeter; import org.apache.bcel.generic.JsrInstruction; import org.apache.bcel.generic.LDC; import org.apache.bcel.generic.MONITOREXIT; import org.apache.bcel.generic.MethodGen; import org.apache.bcel.generic.NEW; import org.apache.bcel.generic.NOP; import org.apache.bcel.generic.POP; import org.apache.bcel.generic.POP2; import org.apache.bcel.generic.PUTFIELD; import org.apache.bcel.generic.PUTSTATIC; import org.apache.bcel.generic.ReturnInstruction; import edu.umd.cs.findbugs.SystemProperties; import edu.umd.cs.findbugs.ba.type.ExceptionSetFactory; import edu.umd.cs.findbugs.ba.type.StandardTypeMerger; import edu.umd.cs.findbugs.bcel.generic.NONNULL2Z; import edu.umd.cs.findbugs.bcel.generic.NULL2Z; import edu.umd.cs.findbugs.classfile.CheckedAnalysisException; import edu.umd.cs.findbugs.classfile.DescriptorFactory; import edu.umd.cs.findbugs.classfile.FieldDescriptor; import edu.umd.cs.findbugs.classfile.Global; import edu.umd.cs.findbugs.classfile.IAnalysisCache; import edu.umd.cs.findbugs.classfile.MethodDescriptor; /** * A CFGBuilder that really tries to construct accurate control flow graphs. The * CFGs it creates have accurate exception edges, and have accurately inlined * JSR subroutines. * * @author David Hovemeyer * @see CFG */ public class BetterCFGBuilder2 implements CFGBuilder, EdgeTypes, Debug { private static final boolean DEBUG = SystemProperties.getBoolean("cfgbuilder.debug"); /* * ---------------------------------------------------------------------- * Helper classes * ---------------------------------------------------------------------- */ /** * A work list item for creating the CFG for a subroutine. */ private static class WorkListItem { private final InstructionHandle start; private final BasicBlock basicBlock; /** * Constructor. * * @param start * first instruction in the basic block * @param basicBlock * the basic block to build */ public WorkListItem(InstructionHandle start, BasicBlock basicBlock) { this.start = start; this.basicBlock = basicBlock; } /** * Get the start instruction. */ public InstructionHandle getStartInstruction() { return start; } /** * Get the basic block. */ public BasicBlock getBasicBlock() { return basicBlock; } } /** * A placeholder for a control edge that escapes its subroutine to return * control back to an outer (calling) subroutine. It will turn into a real * edge during inlining. */ private static class EscapeTarget { private final InstructionHandle target; private final @Edge.Type int edgeType; /** * Constructor. * * @param target * the target instruction in a calling subroutine * @param edgeType * the type of edge that should be created when the * subroutine is inlined into its calling context */ public EscapeTarget(InstructionHandle target, @Edge.Type int edgeType) { this.target = target; this.edgeType = edgeType; } /** * Get the target instruction. */ public InstructionHandle getTarget() { return target; } /** * Get the edge type. */ public @Edge.Type int getEdgeType() { return edgeType; } } /** * JSR subroutine. The top level subroutine is where execution starts. Each * subroutine has its own CFG. Eventually, all JSR subroutines will be * inlined into the top level subroutine, resulting in an accurate CFG for * the overall method. */ private class Subroutine { private final InstructionHandle start; private final BitSet instructionSet; private final CFG cfg; private final IdentityHashMap<InstructionHandle, BasicBlock> blockMap; private final IdentityHashMap<BasicBlock, List<EscapeTarget>> escapeTargetListMap; private final BitSet returnBlockSet; private final BitSet exitBlockSet; private final BitSet unhandledExceptionBlockSet; private final LinkedList<WorkListItem> workList; /** * Constructor. * * @param start * the start instruction for the subroutine */ public Subroutine(InstructionHandle start) { this.start = start; this.instructionSet = new BitSet(); this.cfg = new CFG(); this.blockMap = new IdentityHashMap<InstructionHandle, BasicBlock>(); this.escapeTargetListMap = new IdentityHashMap<BasicBlock, List<EscapeTarget>>(); this.returnBlockSet = new BitSet(); this.exitBlockSet = new BitSet(); this.unhandledExceptionBlockSet = new BitSet(); this.workList = new LinkedList<WorkListItem>(); } /** * Get the start instruction. */ public InstructionHandle getStartInstruction() { return start; } /** * Allocate a new basic block in the subroutine. */ public BasicBlock allocateBasicBlock() { return cfg.allocate(); } /** * Add a work list item for a basic block to be constructed. */ public void addItem(WorkListItem item) { workList.add(item); } /** * Are there more work list items? */ public boolean hasMoreWork() { return !workList.isEmpty(); } /** * Get the next work list item. */ public WorkListItem nextItem() { return workList.removeFirst(); } /** * Get the entry block for the subroutine's CFG. */ public BasicBlock getEntry() { return cfg.getEntry(); } /** * Get the exit block for the subroutine's CFG. */ public BasicBlock getExit() { return cfg.getExit(); } /** * Get the start block for the subroutine's CFG. (I.e., the block * containing the start instruction.) */ public BasicBlock getStartBlock() { return getBlock(start); } /** * Get the subroutine's CFG. */ public CFG getCFG() { return cfg; } /** * Add an instruction to the subroutine. We keep track of which * instructions are part of which subroutines. No instruction may be * part of more than one subroutine. * * @param handle * the instruction to be added to the subroutine */ public void addInstruction(InstructionHandle handle) throws CFGBuilderException { int position = handle.getPosition(); if (usedInstructionSet.get(position)) throw new CFGBuilderException("Instruction " + handle + " visited in multiple subroutines"); instructionSet.set(position); usedInstructionSet.set(position); } /** * Is the given instruction part of this subroutine? */ public boolean containsInstruction(InstructionHandle handle) { return instructionSet.get(handle.getPosition()); } /** * Get the basic block in the subroutine for the given instruction. If * the block doesn't exist yet, it is created, and a work list item is * added which will populate it. Note that if start is an exception * thrower, the block returned will be its ETB. * * @param start * the start instruction for the block * @return the basic block for the instruction */ public BasicBlock getBlock(InstructionHandle start) { BasicBlock block = blockMap.get(start); if (block == null) { block = allocateBasicBlock(); blockMap.put(start, block); // Block is an exception handler? CodeExceptionGen exceptionGen = exceptionHandlerMap.getHandlerForStartInstruction(start); if (exceptionGen != null) block.setExceptionGen(null, exceptionGen); addItem(new WorkListItem(start, block)); } return block; } /** * Indicate that the method returns at the end of the given block. * * @param block * the returning block */ public void setReturnBlock(BasicBlock block) { returnBlockSet.set(block.getLabel()); } /** * Does the method return at the end of this block? */ public boolean isReturnBlock(BasicBlock block) { return returnBlockSet.get(block.getLabel()); } /** * Indicate that System.exit() is called at the end of the given block. * * @param block * the exiting block */ public void setExitBlock(BasicBlock block) { exitBlockSet.set(block.getLabel()); } /** * Is System.exit() called at the end of this block? */ public boolean isExitBlock(BasicBlock block) { return exitBlockSet.get(block.getLabel()); } /** * Indicate that an unhandled exception may be thrown by the given * block. * * @param block * the block throwing an unhandled exception */ public void setUnhandledExceptionBlock(BasicBlock block) { unhandledExceptionBlockSet.set(block.getLabel()); } /** * Does this block throw an unhandled exception? */ public boolean isUnhandledExceptionBlock(BasicBlock block) { return unhandledExceptionBlockSet.get(block.getLabel()); } /** * Add a control flow edge to the subroutine. If the control target has * not yet been added to the subroutine, a new work list item is added. * If the control target is in another subroutine, an EscapeTarget is * added. * * @param sourceBlock * the source basic block * @param target * the control target * @param edgeType * the type of control edge */ public void addEdgeAndExplore(BasicBlock sourceBlock, InstructionHandle target, @Edge.Type int edgeType) { if (usedInstructionSet.get(target.getPosition()) && !containsInstruction(target)) { // Control escapes this subroutine List<EscapeTarget> escapeTargetList = escapeTargetListMap.get(sourceBlock); if (escapeTargetList == null) { escapeTargetList = new LinkedList<EscapeTarget>(); escapeTargetListMap.put(sourceBlock, escapeTargetList); } escapeTargetList.add(new EscapeTarget(target, edgeType)); } else { // Edge within the current subroutine BasicBlock targetBlock = getBlock(target); addEdge(sourceBlock, targetBlock, edgeType); } } /** * Add an edge to the subroutine's CFG. * * @param sourceBlock * the source basic block * @param destBlock * the destination basic block * @param edgeType * the type of edge */ public void addEdge(BasicBlock sourceBlock, BasicBlock destBlock, @Edge.Type int edgeType) { if (VERIFY_INTEGRITY) { if (destBlock.isExceptionHandler() && edgeType != HANDLED_EXCEPTION_EDGE) throw new IllegalStateException("In method " + SignatureConverter.convertMethodSignature(methodGen) + ": exception handler " + destBlock.getFirstInstruction() + " reachable by non exception edge type " + edgeType); } cfg.createEdge(sourceBlock, destBlock, edgeType); } /** * Get an Iterator over the EscapeTargets of given basic block. * * @param sourceBlock * the basic block * @return an Iterator over the EscapeTargets */ public Iterator<EscapeTarget> escapeTargetIterator(BasicBlock sourceBlock) { List<EscapeTarget> escapeTargetList = escapeTargetListMap.get(sourceBlock); if (escapeTargetList == null) escapeTargetList = Collections.emptyList(); return escapeTargetList.iterator(); } } /** * Inlining context. This essentially consists of a inlining site and a * subroutine to be inlined. A stack of calling contexts is maintained in * order to resolve EscapeTargets. */ private static class Context { private final Context caller; private final Subroutine subroutine; private final CFG result; private final IdentityHashMap<BasicBlock, BasicBlock> blockMap; private final LinkedList<BasicBlock> workList; /** * Constructor. * * @param caller * the calling context * @param subroutine * the subroutine being inlined * @param result * the result CFG */ public Context(@Nullable Context caller, Subroutine subroutine, CFG result) { this.caller = caller; this.subroutine = subroutine; this.result = result; this.blockMap = new IdentityHashMap<BasicBlock, BasicBlock>(); this.workList = new LinkedList<BasicBlock>(); } /** * Get the calling context. */ public Context getCaller() { return caller; } /** * Get the subroutine being inlined. */ public Subroutine getSubroutine() { return subroutine; } /** * Get the result CFG. */ public CFG getResult() { return result; } /** * Add a basic block to the inlining work list. */ public void addItem(BasicBlock item) { workList.add(item); } /** * Are there more work list items? */ public boolean hasMoreWork() { return !workList.isEmpty(); } /** * Get the next work list item (basic block to be inlined). */ public BasicBlock nextItem() { return workList.removeFirst(); } /** * Map a basic block in a subroutine to the corresponding block in the * resulting CFG. * * @param subBlock * the subroutine block * @param resultBlock * the result CFG block */ public void mapBlock(BasicBlock subBlock, BasicBlock resultBlock) { blockMap.put(subBlock, resultBlock); } /** * Get the block in the result CFG corresponding to the given subroutine * block. * * @param subBlock * the subroutine block * @return the result CFG block */ public BasicBlock getBlock(BasicBlock subBlock) { BasicBlock resultBlock = blockMap.get(subBlock); if (resultBlock == null) { resultBlock = result.allocate(); blockMap.put(subBlock, resultBlock); workList.add(subBlock); } return resultBlock; } /** * Check to ensure that this context is not the result of recursion. */ public void checkForRecursion() throws CFGBuilderException { Context callerContext = caller; while (callerContext != null) { if (callerContext.subroutine == this.subroutine) throw new CFGBuilderException("JSR recursion detected!"); callerContext = callerContext.caller; } } } /* * ---------------------------------------------------------------------- * Instance data * ---------------------------------------------------------------------- */ private final MethodGen methodGen; private final ConstantPoolGen cpg; private final ExceptionHandlerMap exceptionHandlerMap; private final BitSet usedInstructionSet; private final LinkedList<Subroutine> subroutineWorkList; private final IdentityHashMap<InstructionHandle, Subroutine> jsrSubroutineMap; private final Map<FieldDescriptor, Integer> addedFields = new HashMap<FieldDescriptor, Integer>(); private Subroutine topLevelSubroutine; private CFG cfg; /* * ---------------------------------------------------------------------- * Public methods * ---------------------------------------------------------------------- */ /** * Constructor. * * @param methodGen * the method to build a CFG for */ public BetterCFGBuilder2(@Nonnull MethodDescriptor descriptor, @Nonnull MethodGen methodGen) { this.methodGen = methodGen; this.cpg = methodGen.getConstantPool(); IAnalysisCache analysisCache = Global.getAnalysisCache(); StandardTypeMerger merger = null; ExceptionSetFactory exceptionSetFactory; try { exceptionSetFactory = analysisCache.getMethodAnalysis(ExceptionSetFactory.class, descriptor); merger = new StandardTypeMerger( AnalysisContext.currentAnalysisContext() .getLookupFailureCallback(), exceptionSetFactory); } catch (CheckedAnalysisException e) { AnalysisContext.logError("Unable to generate exceptionSetFactory for " + descriptor, e); } this.exceptionHandlerMap = new ExceptionHandlerMap(methodGen, merger); this.usedInstructionSet = new BitSet(); this.jsrSubroutineMap = new IdentityHashMap<InstructionHandle, Subroutine>(); this.subroutineWorkList = new LinkedList<Subroutine>(); } public int getIndex(FieldDescriptor f) { Integer i = addedFields.get(f); if (i != null) return i; int index = cpg.addFieldref(f.getSlashedClassName(), f.getName(), f.getSignature()); addedFields.put(f, index); return index; } public void optimize(InstructionList instructionList) { InstructionHandle head = instructionList.getStart(); while (head != null) { Instruction i = head.getInstruction(); if (i instanceof INVOKESTATIC) { INVOKESTATIC is = (INVOKESTATIC) i; String name = is.getMethodName(cpg); String signature = is.getSignature(cpg); if (name.startsWith("access$")) { XMethod invoked = XFactory.createXMethod(is, cpg); FieldDescriptor field = invoked.getAccessMethodForField(); if (field != null) { boolean isSetter = signature.endsWith("V"); Instruction replacement; int index = getIndex(field); if (field.isStatic()) { if (isSetter) replacement = new PUTSTATIC(index); else replacement = new GETSTATIC(index); } else { if (isSetter) replacement = new PUTFIELD(index); else replacement = new GETFIELD(index); } head.swapInstruction(replacement); if (false) System.out.println("Substituting " + (isSetter ? "set" : "get") + " of " + field + " for call of " + invoked + " in " + methodGen.getClassName() + "." + methodGen.getName() + methodGen.getSignature()); } } } if (i instanceof IfInstruction) { IfInstruction ii = (IfInstruction) i; InstructionHandle target = ii.getTarget(); InstructionHandle next = head.getNext(); if (target.equals(next)) { int consumed = ii.consumeStack(methodGen.getConstantPool()); if (consumed != 1 && consumed != 2) throw new IllegalStateException(); head.swapInstruction(consumed == 1 ? new POP() : new POP2()); } } if (i instanceof IFNULL || i instanceof IFNONNULL) { IfInstruction ii = (IfInstruction) i; InstructionHandle target = ii.getTarget(); InstructionHandle next1 = head.getNext(); // ICONST if (next1 == null) break; if (next1.getInstruction() instanceof ICONST) { InstructionHandle next2 = next1.getNext(); // GOTO if (next2 == null) break; InstructionHandle next3 = next2.getNext(); // ICONST if (next3== null) break; InstructionHandle next4 = next3.getNext(); if (next4 == null) break; if (target.equals(next3) && next2.getInstruction() instanceof GOTO && next3.getInstruction() instanceof ICONST && next1.getTargeters().length == 0 && next2.getTargeters().length == 0 && next3.getTargeters().length == 1 && next4.getTargeters().length == 1) { int c1 = ((ICONST) next1.getInstruction()).getValue().intValue(); GOTO g = (GOTO) next2.getInstruction(); int c2 = ((ICONST) next3.getInstruction()).getValue().intValue(); if (g.getTarget().equals(next4) && (c1 == 1 && c2 == 0 || c1 == 0 && c2 == 1)) { boolean nullIsTrue = i instanceof IFNULL && c2 == 1 || i instanceof IFNONNULL && c2 == 0; if (nullIsTrue) { // System.out.println("Found NULL2Z instruction"); head.swapInstruction(new NULL2Z()); } else { // System.out.println("Found NONNULL2Z instruction"); head.swapInstruction(new NONNULL2Z()); } next3.removeAllTargeters(); next4.removeAllTargeters(); next1.swapInstruction(new NOP()); next2.swapInstruction(new NOP()); next3.swapInstruction(new NOP()); } } } } if (i instanceof ACONST_NULL) { InstructionHandle next = head.getNext(); assert next != null; InstructionHandle next2 = next.getNext(); if (next2 != null && next.getInstruction() instanceof ALOAD) { Instruction check = next2.getInstruction(); if (check instanceof IF_ACMPNE || check instanceof IF_ACMPEQ) { // need to update head.swapInstruction(new NOP()); IfInstruction ifTest = (IfInstruction) check; if (check instanceof IF_ACMPNE) next2.swapInstruction(new IFNONNULL(ifTest.getTarget())); else next2.swapInstruction(new IFNULL(ifTest.getTarget())); } } } head = head.getNext(); } } public void build() throws CFGBuilderException { InstructionList instructionList = methodGen.getInstructionList(); optimize(instructionList); topLevelSubroutine = new Subroutine(instructionList.getStart()); subroutineWorkList.add(topLevelSubroutine); // Build top level subroutine and all JSR subroutines while (!subroutineWorkList.isEmpty()) { Subroutine subroutine = subroutineWorkList.removeFirst(); if (DEBUG) System.out.println("Starting subroutine " + subroutine.getStartInstruction()); build(subroutine); } // Inline everything into the top level subroutine cfg = inlineAll(); // Add a NOP instruction to the entry block. // This allows analyses to construct a Location // representing the entry to the method. BasicBlock entryBlock = cfg.getEntry(); InstructionList il = new InstructionList(); entryBlock.addInstruction(il.append(new NOP())); if (VERIFY_INTEGRITY) cfg.checkIntegrity(); if (true) { cfg.checkIntegrity(); } } public CFG getCFG() { return cfg; } /* * ---------------------------------------------------------------------- * Implementation * ---------------------------------------------------------------------- */ /** * Build a subroutine. We iteratively add basic blocks to the subroutine * until there are no more blocks reachable from the calling context. As JSR * instructions are encountered, new Subroutines are added to the subroutine * work list. * * @param subroutine * the subroutine */ private void build(Subroutine subroutine) throws CFGBuilderException { // Prime the work list subroutine.addEdgeAndExplore(subroutine.getEntry(), subroutine.getStartInstruction(), START_EDGE); // Keep going until all basic blocks in the subroutine have been added while (subroutine.hasMoreWork()) { WorkListItem item = subroutine.nextItem(); InstructionHandle handle = item.getStartInstruction(); BasicBlock basicBlock = item.getBasicBlock(); // Add exception handler block (ETB) for exception-throwing // instructions if (isPEI(handle)) { if (DEBUG) System.out.println("ETB block " + basicBlock.getLabel() + " for " + handle); handleExceptions(subroutine, handle, basicBlock); BasicBlock body = subroutine.allocateBasicBlock(); subroutine.addEdge(basicBlock, body, FALL_THROUGH_EDGE); basicBlock = body; } if (DEBUG) System.out.println("BODY block " + basicBlock.getLabel() + " for " + handle); if (!basicBlock.isEmpty()) throw new IllegalStateException("Block isn't empty!"); // Add instructions until we get to the end of the block boolean endOfBasicBlock = false; do { Instruction ins = handle.getInstruction(); // Add the instruction to the block if (DEBUG) System.out.println("BB " + basicBlock.getLabel() + ": adding" + handle); basicBlock.addInstruction(handle); subroutine.addInstruction(handle); short opcode = ins.getOpcode(); // TODO: should check instruction to ensure that in a JSR // subroutine // no assignments are made to the local containing the return // address. // if (ins instanceof ASTORE) ... if (opcode == Constants.JSR || opcode == Constants.JSR_W) { // Find JSR subroutine, add it to subroutine work list if // we haven't built a CFG for it yet JsrInstruction jsr = (JsrInstruction) ins; InstructionHandle jsrTarget = jsr.getTarget(); Subroutine jsrSubroutine = jsrSubroutineMap.get(jsrTarget); if (jsrSubroutine == null) { jsrSubroutine = new Subroutine(jsrTarget); jsrSubroutineMap.put(jsrTarget, jsrSubroutine); subroutineWorkList.add(jsrSubroutine); } // This ends the basic block. // Add a JSR_EDGE to the successor. // It will be replaced later by the inlined JSR subroutine. subroutine.addEdgeAndExplore(basicBlock, handle.getNext(), JSR_EDGE); endOfBasicBlock = true; } else if (opcode == Constants.RET) { // End of JSR subroutine subroutine.addEdge(basicBlock, subroutine.getExit(), RET_EDGE); endOfBasicBlock = true; } else { TargetEnumeratingVisitor visitor = new TargetEnumeratingVisitor(handle, cpg); if (visitor.isEndOfBasicBlock()) { endOfBasicBlock = true; // Add control edges as appropriate if (visitor.instructionIsThrow()) { handleExceptions(subroutine, handle, basicBlock); } else if (visitor.instructionIsExit()) { subroutine.setExitBlock(basicBlock); } else if (visitor.instructionIsReturn()) { subroutine.setReturnBlock(basicBlock); } else { Iterator<Target> i = visitor.targetIterator(); while (i.hasNext()) { Target target = i.next(); subroutine.addEdgeAndExplore(basicBlock, target.getTargetInstruction(), target.getEdgeType()); } } } } if (!endOfBasicBlock) { InstructionHandle next = handle.getNext(); if (next == null) throw new CFGBuilderException("Control falls off end of method: " + handle); // Is the next instruction a control merge or a PEI? if (isMerge(next) || isPEI(next)) { subroutine.addEdgeAndExplore(basicBlock, next, FALL_THROUGH_EDGE); endOfBasicBlock = true; } else { // Basic block continues handle = next; } } } while (!endOfBasicBlock); } } /** * Add exception edges for given instruction. * * @param subroutine * the subroutine containing the instruction * @param pei * the instruction which throws an exception * @param etb * the exception thrower block (ETB) for the instruction */ private void handleExceptions(Subroutine subroutine, InstructionHandle pei, BasicBlock etb) { etb.setExceptionThrower(pei); // Remember whether or not a universal exception handler // is reachable. If so, then we know that exceptions raised // at this instruction cannot propagate out of the method. boolean sawUniversalExceptionHandler = false; List<CodeExceptionGen> exceptionHandlerList = exceptionHandlerMap.getHandlerList(pei); if (exceptionHandlerList != null) { for (CodeExceptionGen exceptionHandler : exceptionHandlerList) { InstructionHandle handlerStart = exceptionHandler.getHandlerPC(); subroutine.addEdgeAndExplore(etb, handlerStart, HANDLED_EXCEPTION_EDGE); if (Hierarchy.isUniversalExceptionHandler(exceptionHandler.getCatchType())) sawUniversalExceptionHandler = true; } } // If required, mark this block as throwing an unhandled exception. // For now, we assume that if there is no reachable handler that handles // ANY exception type, then the exception can be thrown out of the // method. if (!sawUniversalExceptionHandler) { if (DEBUG) System.out.println("Adding unhandled exception edge from " + pei); subroutine.setUnhandledExceptionBlock(etb); } } /** * Return whether or not the given instruction can throw exceptions. * * @param handle * the instruction * @return true if the instruction can throw an exception, false otherwise */ private boolean isPEI(InstructionHandle handle) { Instruction ins = handle.getInstruction(); if (!(ins instanceof ExceptionThrower)) return false; if (ins instanceof NEW) return false; // if (ins instanceof ATHROW) return false; if (ins instanceof GETSTATIC) return false; if (ins instanceof PUTSTATIC) return false; if (ins instanceof ReturnInstruction) return false; if (ins instanceof INSTANCEOF) return false; if (ins instanceof MONITOREXIT) return false; if (ins instanceof LDC) return false; return true; } /** * Determine whether or not the given instruction is a control flow merge. * * @param handle * the instruction * @return true if the instruction is a control merge, false otherwise */ private static boolean isMerge(InstructionHandle handle) { if (handle.hasTargeters()) { // Check all targeters of this handle to see if any // of them are branches. If so, the instruction is a merge. InstructionTargeter[] targeterList = handle.getTargeters(); for (InstructionTargeter targeter : targeterList) { if (targeter instanceof BranchInstruction) return true; } } return false; } /** * Inline all JSR subroutines into the top-level subroutine. This produces a * complete CFG for the entire method, in which all JSR subroutines are * inlined. * * @return the CFG for the method */ private CFG inlineAll() throws CFGBuilderException { CFG result = new CFG(); Context rootContext = new Context(null, topLevelSubroutine, result); rootContext.mapBlock(topLevelSubroutine.getEntry(), result.getEntry()); rootContext.mapBlock(topLevelSubroutine.getExit(), result.getExit()); BasicBlock resultStartBlock = rootContext.getBlock(topLevelSubroutine.getStartBlock()); result.createEdge(result.getEntry(), resultStartBlock, START_EDGE); inline(rootContext); return result; } /** * Inline a subroutine into a calling context. * * @param context * the Context */ public void inline(Context context) throws CFGBuilderException { CFG result = context.getResult(); // Check to ensure we're not trying to inline something that is // recursive context.checkForRecursion(); Subroutine subroutine = context.getSubroutine(); CFG subCFG = subroutine.getCFG(); while (context.hasMoreWork()) { BasicBlock subBlock = context.nextItem(); BasicBlock resultBlock = context.getBlock(subBlock); // Mark blocks which are in JSR subroutines resultBlock.setInJSRSubroutine(context.getCaller() != null); // Copy instructions into the result block BasicBlock.InstructionIterator insIter = subBlock.instructionIterator(); while (insIter.hasNext()) { InstructionHandle handle = insIter.next(); resultBlock.addInstruction(handle); } // Set exception thrower status if (subBlock.isExceptionThrower()) resultBlock.setExceptionThrower(subBlock.getExceptionThrower()); // Set exception handler status if (subBlock.isExceptionHandler()) resultBlock.setExceptionGen(null, subBlock.getExceptionGen()); // Add control edges (including inlining JSR subroutines) Iterator<Edge> edgeIter = subCFG.outgoingEdgeIterator(subBlock); while (edgeIter.hasNext()) { Edge edge = edgeIter.next(); int edgeType = edge.getType(); if (edgeType == JSR_EDGE) { // Inline a JSR subroutine... // Create a new Context InstructionHandle jsrHandle = subBlock.getLastInstruction(); JsrInstruction jsr = (JsrInstruction) jsrHandle.getInstruction(); Subroutine jsrSub = jsrSubroutineMap.get(jsr.getTarget()); Context jsrContext = new Context(context, jsrSub, context.getResult()); // The start block in the JSR subroutine maps to the first // inlined block in the result CFG. BasicBlock resultJSRStartBlock = jsrContext.getBlock(jsrSub.getStartBlock()); result.createEdge(resultBlock, resultJSRStartBlock, GOTO_EDGE); // The exit block in the JSR subroutine maps to the result // block // corresponding to the instruction following the JSR. // (I.e., that is where control returns after the execution // of // the JSR subroutine.) BasicBlock subJSRSuccessorBlock = subroutine.getBlock(jsrHandle.getNext()); BasicBlock resultJSRSuccessorBlock = context.getBlock(subJSRSuccessorBlock); jsrContext.mapBlock(jsrSub.getExit(), resultJSRSuccessorBlock); // Inline the JSR subroutine inline(jsrContext); } else { // Ordinary control edge BasicBlock resultTarget = context.getBlock(edge.getTarget()); result.createEdge(resultBlock, resultTarget, edge.getType()); } } // Add control edges for escape targets Iterator<EscapeTarget> escapeTargetIter = subroutine.escapeTargetIterator(subBlock); while (escapeTargetIter.hasNext()) { EscapeTarget escapeTarget = escapeTargetIter.next(); InstructionHandle targetInstruction = escapeTarget.getTarget(); // Look for the calling context which has the target instruction Context caller = context.getCaller(); while (caller != null) { if (caller.getSubroutine().containsInstruction(targetInstruction)) break; caller = caller.getCaller(); } if (caller == null) throw new CFGBuilderException("Unknown caller for escape target " + targetInstruction + " referenced by " + context.getSubroutine().getStartInstruction()); // Find result block in caller BasicBlock subCallerTargetBlock = caller.getSubroutine().getBlock(targetInstruction); BasicBlock resultCallerTargetBlock = caller.getBlock(subCallerTargetBlock); // Add an edge to caller context result.createEdge(resultBlock, resultCallerTargetBlock, escapeTarget.getEdgeType()); } // If the block returns from the method, add a return edge if (subroutine.isReturnBlock(subBlock)) { result.createEdge(resultBlock, result.getExit(), RETURN_EDGE); } // If the block calls System.exit(), add an exit edge if (subroutine.isExitBlock(subBlock)) { result.createEdge(resultBlock, result.getExit(), EXIT_EDGE); } // If the block throws an unhandled exception, add an unhandled // exception edge if (subroutine.isUnhandledExceptionBlock(subBlock)) { result.createEdge(resultBlock, result.getExit(), UNHANDLED_EXCEPTION_EDGE); } } /* * while (blocks are left) { * * get block from subroutine get corresponding block from result copy * instructions into result block * * if (block terminated by JSR) { get JSR subroutine create new context * create GOTO edge from current result block to start block of new * inlined context map subroutine exit block to result JSR successor * block inline (new context, result) } else { for each outgoing edge { * map each target to result blocks (add block to to work list if * needed) add edges to result } * * for each outgoing escape target { add edges into blocks in outer * contexts (adding those blocks to outer work list if needed) } * * if (block returns) { add return edge from result block to result CFG * exit block } * * if (block calls System.exit()) { add exit edge from result block to * result CFG exit block } * * if (block throws unhandled exception) { add unhandled exception edge * from result block to result CFG exit block } } * * } */ } /** * Test driver. */ public static void main(String[] argv) throws Exception { if (argv.length != 1) { System.err.println("Usage: " + BetterCFGBuilder2.class.getName() + " <class file>"); System.exit(1); } String methodName = SystemProperties.getProperty("cfgbuilder.method"); JavaClass jclass = new ClassParser(argv[0]).parse(); ClassGen classGen = new ClassGen(jclass); Method[] methodList = jclass.getMethods(); for (Method method : methodList) { if (method.isAbstract() || method.isNative()) continue; if (methodName != null && !method.getName().equals(methodName)) continue; MethodDescriptor descriptor = DescriptorFactory.instance().getMethodDescriptor(jclass, method); MethodGen methodGen = new MethodGen(method, jclass.getClassName(), classGen.getConstantPool()); CFGBuilder cfgBuilder = new BetterCFGBuilder2(descriptor, methodGen); cfgBuilder.build(); CFG cfg = cfgBuilder.getCFG(); CFGPrinter cfgPrinter = new CFGPrinter(cfg); System.out.println("---------------------------------------------------------------------"); System.out.println("Method: " + SignatureConverter.convertMethodSignature(methodGen)); System.out.println("---------------------------------------------------------------------"); cfgPrinter.print(System.out); } } } // vim:ts=4