/*
* 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.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
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.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.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.PUTSTATIC;
import org.apache.bcel.generic.ReturnInstruction;
import edu.umd.cs.findbugs.SystemProperties;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.bcel.generic.NONNULL2Z;
import edu.umd.cs.findbugs.bcel.generic.NULL2Z;
import edu.umd.cs.findbugs.visitclass.PrintClass;
/**
* 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;
}
}
private static final LinkedList<EscapeTarget> emptyEscapeTargetList = new LinkedList<EscapeTarget>();
/**
* 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 IdentityHashMap<InstructionHandle, BasicBlock> blockMap;
private IdentityHashMap<BasicBlock, List<EscapeTarget>> escapeTargetListMap;
private BitSet returnBlockSet;
private BitSet exitBlockSet;
private BitSet unhandledExceptionBlockSet;
private 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(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, 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, 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 = emptyEscapeTargetList;
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 MethodGen methodGen;
private ConstantPoolGen cpg;
private ExceptionHandlerMap exceptionHandlerMap;
private BitSet usedInstructionSet;
private LinkedList<Subroutine> subroutineWorkList;
private IdentityHashMap<InstructionHandle, Subroutine> jsrSubroutineMap;
private Subroutine topLevelSubroutine;
private CFG cfg;
/*
* ----------------------------------------------------------------------
* Public methods
* ----------------------------------------------------------------------
*/
/**
* Constructor.
*
* @param methodGen
* the method to build a CFG for
*/
public BetterCFGBuilder2(@NonNull MethodGen methodGen) {
this.methodGen = methodGen;
this.cpg = methodGen.getConstantPool();
this.exceptionHandlerMap = new ExceptionHandlerMap(methodGen);
this.usedInstructionSet = new BitSet();
this.jsrSubroutineMap = new IdentityHashMap<InstructionHandle, Subroutine>();
this.subroutineWorkList = new LinkedList<Subroutine>();
}
public void optimize(InstructionList instructionList) {
InstructionHandle head = instructionList.getStart();
while (head != null) {
Instruction i = head.getInstruction();
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(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;
MethodGen methodGen = new MethodGen(method, jclass.getClassName(), classGen.getConstantPool());
CFGBuilder cfgBuilder = new BetterCFGBuilder2(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