/* Copyright (c) 2006, Sriram Srinivasan
*
* You may distribute this software under the terms of the license
* specified in the file "License"
*/
package kilim.analysis;
import static kilim.Constants.NOT_PAUSABLE_CLASS;
import static kilim.Constants.PAUSABLE_CLASS;
import static kilim.analysis.BasicBlock.COALESCED;
import static kilim.analysis.BasicBlock.ENQUEUED;
import static kilim.analysis.BasicBlock.INLINE_CHECKED;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_VOLATILE;
import static org.objectweb.asm.Opcodes.JSR;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.TreeMap;
import kilim.KilimException;
import kilim.mirrors.Detector;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
/**
* This represents all the basic blocks of a method.
*/
public class MethodFlow extends MethodNode {
/**
* The classFlow to which this methodFlow belongs
*/
ClassFlow classFlow;
/**
* Maps instructions[i] to LabelNode or null (if no label). Note that
* LabelInsnNodes are not accounted for here because they themselves are not
* labelled.
*/
private ArrayList<LabelNode> posToLabelMap;
/**
* Reverse map of posToLabelMap. Maps Labels to index within
* method.instructions.
*/
private HashMap<LabelNode, Integer> labelToPosMap;
/**
* Maps labels to BasicBlocks
*/
private HashMap<LabelNode, BasicBlock> labelToBBMap;
/**
* The list of basic blocks, in the order in which they occur in the class file.
* Maintaining this order is important, because we'll use it to drive duplication (in case
* of JSRs) and also while writing out the class file.
*/
private BBList basicBlocks;
private PriorityQueue<BasicBlock> workset;
private boolean hasPausableAnnotation;
private boolean suppressPausableCheck;
private List<MethodInsnNode> pausableMethods = new LinkedList<MethodInsnNode>();
private final Detector detector;
private TreeMap<Integer, LineNumberNode> lineNumberNodes = new TreeMap<Integer, LineNumberNode>();
private HashMap<Integer, FrameNode> frameNodes = new HashMap<Integer, FrameNode>();
public MethodFlow(
ClassFlow classFlow,
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions,
final Detector detector) {
super(access, name, desc, signature, exceptions);
this.classFlow = classFlow;
this.detector = detector;
posToLabelMap = new ArrayList<LabelNode>();
labelToPosMap = new HashMap<LabelNode, Integer>();
labelToBBMap = new HashMap<LabelNode, BasicBlock>();
if (exceptions != null && exceptions.length > 0) {
for (String e: exceptions) {
if (e.equals(PAUSABLE_CLASS)) {
hasPausableAnnotation = true;
break;
} else if (e.equals(NOT_PAUSABLE_CLASS)) {
suppressPausableCheck = true;
}
}
}
}
public void restoreNonInstructionNodes() {
InsnList newinsns = new InsnList();
int sz = instructions.size();
for (int i = 0; i < sz; i++) {
LabelNode l = getLabelAt(i);
if (l != null) {
newinsns.add(l);
}
LineNumberNode ln = lineNumberNodes.get(i);
if (ln != null) {
newinsns.add(ln);
}
AbstractInsnNode ain = instructions.get(i);
newinsns.add(ain);
}
LabelNode l = getLabelAt(sz);
if (l != null) {
newinsns.add(l);
}
LineNumberNode ln = lineNumberNodes.get(sz);
if (ln != null) {
newinsns.add(ln);
}
super.instructions = newinsns;
}
public void analyze() throws KilimException {
buildBasicBlocks();
if (basicBlocks.size() == 0) return;
consolidateBasicBlocks();
assignCatchHandlers();
inlineSubroutines();
doLiveVarAnalysis();
dataFlow();
this.labelToBBMap = null; // we don't need this mapping anymore
}
public void verifyPausables() throws KilimException {
// If we are looking at a woven file, we don't need to verify
// anything
if (classFlow.isWoven || suppressPausableCheck) return;
if (!hasPausableAnnotation && !pausableMethods.isEmpty()) {
String msg;
String name = toString(classFlow.getClassName(),this.name,this.desc);
if (this.name.endsWith("init>")) {
msg = "Constructor " + name + " calls pausable methods:\n";
} else {
msg = name + " should be marked pausable. It calls pausable methods\n";
}
for (MethodInsnNode min: pausableMethods) {
msg += toString(min.owner, min.name, min.desc) + '\n';
}
throw new KilimException(msg);
}
if (classFlow.superName != null) {
checkStatus(classFlow.superName, name, desc);
}
if (classFlow.interfaces != null) {
for (Object ifc: classFlow.interfaces) {
checkStatus((String) ifc, name, desc);
}
}
}
private void checkStatus(String superClassName, String methodName, String desc) throws KilimException {
int status = detector.getPausableStatus(superClassName, methodName, desc);
if ((status == Detector.PAUSABLE_METHOD_FOUND && !hasPausableAnnotation)) {
throw new KilimException("Base class method is pausable, derived class is not: " +
"\nBase class = " + superClassName +
"\nDerived class = " + this.classFlow.name +
"\nMethod = " + methodName + desc);
}
if (status == Detector.METHOD_NOT_PAUSABLE && hasPausableAnnotation) {
throw new KilimException("Base class method is not pausable, but derived class is: " +
"\nBase class = " + superClassName +
"\nDerived class = " + this.classFlow.name +
"\nMethod = " + methodName + desc);
}
}
private String toString(String className, String methName, String desc) {
return className.replace('/', '.') + '.' + methName + desc;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
super.visitMethodInsn(opcode, owner, name, desc);
// The only reason for adding to pausableMethods is to create a BB for pausable
// method call sites. If the class is already woven, we don't need this
// functionality.
if (!classFlow.isWoven) {
int methodStatus = detector.getPausableStatus(owner, name, desc);
if (methodStatus == Detector.PAUSABLE_METHOD_FOUND) {
MethodInsnNode min = (MethodInsnNode)instructions.get(instructions.size()-1);
pausableMethods.add(min);
}
}
}
@Override
public void visitLabel(Label label) {
setLabel(instructions.size(), super.getLabelNode(label));
}
@Override
public void visitLineNumber(int line, Label start) {
LabelNode ln = getLabelNode(start);
lineNumberNodes.put(instructions.size(), new LineNumberNode(line, ln));
}
void visitLineNumbers(MethodVisitor mv) {
for (LineNumberNode node : lineNumberNodes.values()) {
mv.visitLineNumber(node.line, node.start.getLabel());
}
}
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack,
Object[] stack) {
frameNodes.put(instructions.size(), new FrameNode(type, nLocal, local, nStack, stack));
}
private void inlineSubroutines() throws KilimException {
markPausableJSRs();
while (true) {
ArrayList<BasicBlock> newBBs = null;
for (BasicBlock bb: basicBlocks) {
if (bb.hasFlag(INLINE_CHECKED)) continue;
bb.setFlag(INLINE_CHECKED);
if (bb.lastInstruction() == JSR) {
newBBs = bb.inline();
if (newBBs != null) {
break;
}
}
}
if (newBBs == null) {
break;
}
int id = basicBlocks.size();
for (BasicBlock bb: newBBs) {
bb.setId(id++);
basicBlocks.add(bb);
}
}
// If there are any pausable subroutines, modify the JSRs/RETs to
// GOTOs
for (BasicBlock bb: basicBlocks) {
bb.changeJSR_RET_toGOTOs();
}
}
private void markPausableJSRs() throws KilimException {
for (BasicBlock bb: basicBlocks) {
bb.checkPausableJSR();
}
}
boolean isPausableMethodInsn(MethodInsnNode min) {
return pausableMethods.contains(min);
}
@Override
public String toString() {
ArrayList<BasicBlock> ret = getBasicBlocks();
Collections.sort(ret);
return ret.toString();
}
public BBList getBasicBlocks() {
return basicBlocks;
}
private void assignCatchHandlers() {
@SuppressWarnings("unchecked")
ArrayList<TryCatchBlockNode> tcbs = (ArrayList<TryCatchBlockNode>) tryCatchBlocks;
/// aargh. I'd love to create an array of Handler objects, but generics
// doesn't care for it.
if (tcbs.size() == 0) return;
ArrayList<Handler> handlers= new ArrayList<Handler>(tcbs.size());
for (int i = 0; i < tcbs.size(); i++) {
TryCatchBlockNode tcb = tcbs.get(i);
handlers.add(new Handler(
getLabelPosition(tcb.start),
getLabelPosition(tcb.end) - 1, // end is inclusive
tcb.type,
getOrCreateBasicBlock(tcb.handler)));
}
for (BasicBlock bb : basicBlocks) {
bb.chooseCatchHandlers(handlers);
}
}
void buildBasicBlocks() {
// preparatory phase
int numInstructions = instructions.size();
basicBlocks = new BBList();
// Note: i modified within the loop
for (int i = 0; i < numInstructions; i++) {
LabelNode l = getOrCreateLabelAtPos(i);
BasicBlock bb = getOrCreateBasicBlock(l);
i = bb.initialize(i); // i now points to the last instruction in bb.
basicBlocks.add(bb);
}
}
/**
* In live var analysis a BB asks its successor (in essence) about which
* vars are live, mixes it with its own uses and defs and passes on a
* new list of live vars to its predecessors. Since the information
* bubbles up the chain, we iterate the list in reverse order, for
* efficiency. We could order the list topologically or do a depth-first
* spanning tree, but it seems like overkill for most bytecode
* procedures. The order of computation doesn't affect the correctness;
* it merely changes the number of iterations to reach a fixpoint.
*/
private void doLiveVarAnalysis() {
ArrayList<BasicBlock> bbs = getBasicBlocks();
Collections.sort(bbs); // sorts in increasing startPos order
boolean changed;
do {
changed = false;
for (int i = bbs.size() - 1; i >= 0; i--) {
changed = bbs.get(i).flowVarUsage() || changed;
}
} while (changed);
}
/**
* In the first pass (buildBasicBlocks()), we create BBs whenever we
* encounter a label. We don't really know until we are done with that
* pass whether a label is the target of a branch instruction or it is
* there because of an exception handler. See coalesceWithFollowingBlock()
* for more detail.
*/
private void consolidateBasicBlocks() {
BBList newBBs = new BBList(basicBlocks.size());
int pos = 0;
for (BasicBlock bb: basicBlocks) {
if (!bb.hasFlag(COALESCED)) {
bb.coalesceTrivialFollowers();
// The original bb's followers should have been marked as processed.
bb.setId(pos++);
newBBs.add(bb);
}
}
basicBlocks = newBBs;
assert checkNoBasicBlockLeftBehind();
}
private boolean checkNoBasicBlockLeftBehind() { // like "no child left behind"
ArrayList<BasicBlock> bbs = basicBlocks;
HashSet<BasicBlock> hs = new HashSet<BasicBlock>(bbs.size() * 2);
hs.addAll(bbs);
int prevBBend = -1;
for (BasicBlock bb: bbs) {
assert bb.isInitialized() : "BB not inited: " + bb;
assert bb.startPos == prevBBend + 1;
for (BasicBlock succ: bb.successors) {
assert succ.isInitialized() : "Basic block not inited: " + succ +"\nSuccessor of " + bb;
assert hs.contains(succ) :
"BB not found:\n" + succ;
}
prevBBend = bb.endPos;
}
assert bbs.get(bbs.size()-1).endPos == instructions.size()-1;
return true;
}
private void dataFlow() {
workset = new PriorityQueue<BasicBlock>(instructions.size(), new BBComparator());
//System.out.println("Method: " + this.name);
BasicBlock startBB = getBasicBlocks().get(0);
assert startBB != null : "Null starting block in flowTypes()";
startBB.startFrame = new Frame(classFlow.getClassDescriptor(), this);
enqueue(startBB);
while (!workset.isEmpty()) {
BasicBlock bb = dequeue();
bb.interpret();
}
}
void setLabel(int pos, LabelNode l) {
for (int i = pos - posToLabelMap.size() + 1; i >= 0; i--) {
// pad with nulls ala perl
posToLabelMap.add(null);
}
posToLabelMap.set(pos, l);
labelToPosMap.put(l, pos);
}
LabelNode getOrCreateLabelAtPos(int pos) {
LabelNode ret = null;
if (pos < posToLabelMap.size()) {
ret = posToLabelMap.get(pos);
}
if (ret == null) {
ret = new LabelNode();
setLabel(pos, ret);
}
return ret;
}
int getLabelPosition(LabelNode l) {
return labelToPosMap.get(l);
}
BasicBlock getOrCreateBasicBlock(LabelNode l) {
BasicBlock ret = labelToBBMap.get(l);
if (ret == null) {
ret = new BasicBlock(this, l);
Object oldVal = labelToBBMap.put(l, ret);
assert oldVal == null : "Duplicate BB created at label";
}
return ret;
}
BasicBlock getBasicBlock(LabelNode l) {
return labelToBBMap.get(l);
}
private BasicBlock dequeue() {
BasicBlock bb = workset.poll();
bb.unsetFlag(ENQUEUED);
return bb;
}
void enqueue(BasicBlock bb) {
assert bb.startFrame != null : "Enqueued null start frame";
if (!bb.hasFlag(ENQUEUED)) {
workset.add(bb);
bb.setFlag(ENQUEUED);
}
}
public LabelNode getLabelAt(int pos) {
return (pos < posToLabelMap.size()) ? posToLabelMap.get(pos) : null;
}
void addInlinedBlock(BasicBlock bb) {
bb.setId(basicBlocks.size());
basicBlocks.add(bb);
}
public int getNumArgs() {
int ret = TypeDesc.getNumArgumentTypes(desc);
if (!isStatic()) ret++;
return ret;
}
public boolean isPausable() {
return hasPausableAnnotation;
}
public void setPausable(boolean isPausable) {
hasPausableAnnotation = isPausable;
}
public static void acceptAnnotation(final AnnotationVisitor av, final String name,
final Object value) {
if (value instanceof String[]) {
String[] typeconst = (String[]) value;
av.visitEnum(name, typeconst[0], typeconst[1]);
} else if (value instanceof AnnotationNode) {
AnnotationNode an = (AnnotationNode) value;
an.accept(av.visitAnnotation(name, an.desc));
} else if (value instanceof List<?>) {
AnnotationVisitor v = av.visitArray(name);
List<?> array = (List<?>) value;
for (int j = 0; j < array.size(); ++j) {
acceptAnnotation(v, null, array.get(j));
}
v.visitEnd();
} else {
av.visit(name, value);
}
}
public boolean isAbstract() {
return ((this.access & Opcodes.ACC_ABSTRACT) != 0);
}
public boolean isStatic() {
return ((this.access & ACC_STATIC) != 0);
}
public boolean isBridge() {
return ((this.access & ACC_VOLATILE) != 0);
}
public Detector detector() {
return this.classFlow.detector();
}
public void resetLabels() {
for (int i = 0; i < posToLabelMap.size(); i++) {
LabelNode ln = posToLabelMap.get(i);
if (ln != null) {
ln.resetLabel();
}
}
}
}