package org.codehaus.groovy.gjit;
/***
* ASM: a very small and fast Java bytecode manipulation framework
* Copyright (c) 2000-2007 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
/**
* A semantic bytecode analyzer. <i>This class does not fully check that JSR and
* RET instructions are valid.</i>
*
* @author Eric Bruneton
* @author Chanwit Kaewkasi
*/
public class Analyzer implements Opcodes {
protected Interpreter interpreter;
private InsnList insns;
private Map<AbstractInsnNode, List<TryCatchBlockNode>> handlers = new HashMap<AbstractInsnNode, List<TryCatchBlockNode>>();
private Map<AbstractInsnNode, Frame> frames = new HashMap<AbstractInsnNode, Frame>();
private Map<AbstractInsnNode, Subroutine> subroutines = new HashMap<AbstractInsnNode, Subroutine>();
private Map<AbstractInsnNode, Boolean> queued = new HashMap<AbstractInsnNode, Boolean>();
private Map<AbstractInsnNode, AbstractInsnNode> queue = new HashMap<AbstractInsnNode, AbstractInsnNode>();
private AbstractInsnNode top;
/**
* Constructs a new {@link Analyzer}.
*
* @param interpreter the interpreter to be used to symbolically interpret
* the bytecode instructions.
*/
public Analyzer(final Interpreter interpreter) {
this.interpreter = interpreter;
}
public Analyzer() {
}
public enum Action {
NONE,
ADD,
REMOVE,
REPLACE
}
/**
* Analyzes the given method.
*
* @param owner the internal name of the class to which the method belongs.
* @param m the method to be analyzed.
* @return the symbolic state of the execution stack frame at each bytecode
* instruction of the method. The size of the returned array is
* equal to the number of instructions (and labels) of the method. A
* given frame is <tt>null</tt> if and only if the corresponding
* instruction cannot be reached (dead code).
* @throws AnalyzerException if a problem occurs during the analysis.
*/
// private enum ExecutionState {
// NORMAL,
// HANDLED,
// HANDLING
// }
// private ExecutionState state = ExecutionState.NORMAL;
private final static Map<AbstractInsnNode, Frame> EMPTY_FRAMES = new HashMap<AbstractInsnNode, Frame>();
public Map<AbstractInsnNode, Frame> analyze(final String owner, final MethodNode m)
throws AnalyzerException
{
if ((m.access & (ACC_ABSTRACT | ACC_NATIVE)) != 0) {
return EMPTY_FRAMES;
}
insns = m.instructions;
if(insns.size() == 0) {
return EMPTY_FRAMES;
}
top = insns.get(0);
// computes exception handlers for each instruction
for (int i = 0; i < m.tryCatchBlocks.size(); ++i) {
TryCatchBlockNode tcb = (TryCatchBlockNode) m.tryCatchBlocks.get(i);
int begin = insns.indexOf(tcb.start);
int end = insns.indexOf(tcb.end);
for (int j = begin; j < end; ++j) {
AbstractInsnNode jth = insns.get(j);
List<TryCatchBlockNode> insnHandlers = handlers.get(jth);
if (insnHandlers == null) {
insnHandlers = new ArrayList<TryCatchBlockNode>();
handlers.put(jth, insnHandlers);
}
insnHandlers.add(tcb);
}
}
// computes the subroutine for each instruction:
Subroutine main = new Subroutine(null, m.maxLocals, null);
List<AbstractInsnNode> subroutineCalls = new ArrayList<AbstractInsnNode>();
Map<LabelNode, Subroutine> subroutineHeads = new HashMap<LabelNode, Subroutine>();
findSubroutine(top, main, subroutineCalls);
while (!subroutineCalls.isEmpty()) {
JumpInsnNode jsr = (JumpInsnNode) subroutineCalls.remove(0);
Subroutine sub = subroutineHeads.get(jsr.label);
if (sub == null) {
sub = new Subroutine(jsr.label, m.maxLocals, jsr);
subroutineHeads.put(jsr.label, sub);
findSubroutine(jsr.label, sub, subroutineCalls);
} else {
sub.callers.add(jsr);
}
}
for (int i = 0; i < insns.size(); ++i) {
AbstractInsnNode in = insns.get(i);
if(subroutines.containsKey(in) && subroutines.get(in).start == null) {
subroutines.remove(in);
}
}
// initializes the data structures for the control flow analysis
Frame current = newFrame(m.maxLocals, m.maxStack);
Frame handler = newFrame(m.maxLocals, m.maxStack);
Type[] args = Type.getArgumentTypes(m.desc);
int local = 0;
if ((m.access & ACC_STATIC) == 0) {
Type ctype = Type.getObjectType(owner);
current.setLocal(local++, interpreter.newValue(ctype));
}
for (int i = 0; i < args.length; ++i) {
current.setLocal(local++, interpreter.newValue(args[i]));
if (args[i].getSize() == 2) {
current.setLocal(local++, interpreter.newValue(null));
}
}
while (local < m.maxLocals) {
current.setLocal(local++, interpreter.newValue(null));
}
merge(top, current, null);
// AbstractInsnNode bookmarkNode = null;
// AbstractInsnNode rollbackNode = null;
List<AbstractInsnNode> finished = new ArrayList<AbstractInsnNode>();
// Map<AbstractInsnNode,AbstractInsnNode> saveTops = new HashMap<AbstractInsnNode, AbstractInsnNode>();
// control flow analysis
while (top != null) {
AbstractInsnNode insn=null;
top = top.getPrevious();
insn = queue.get(top);
if(insn == null) insn = insns.get(0); // because default value in queue will be always 0
// switch(state) {
// case NORMAL:
// saveTops.put(insn, top);
// case HANDLING:
// top = top.getPrevious();
// insn = queue.get(top);
// if(insn == null) insn = insns.get(0); // because default value in queue will be always 0
// break;
// case HANDLED:
// //top = saveTop;
// //insn = rollbackNode;
// top = saveTops.get(rollbackNode);
// insn = rollbackNode;
// for(int i=0;i<finished.size(); i++) {
// queued.put(finished.get(i), Boolean.TRUE);
// }
// finished.clear();
// if(insn == null) insn = insns.get(0); // because default value in queue will be always 0
// rollbackNode = null;
// state = ExecutionState.HANDLING;
// break;
// }
Frame f = null;//frames.get(insn);
Subroutine subroutine = null; //subroutines.get(insn);
// queued.put(insn, Boolean.FALSE);
try {
// DebugUtils.println(insn);
int oldIndex = insns.indexOf(insn);
// DebugUtils.println(">> ORG F: at " + oldIndex + "]["+ f);
boolean done = false;
while(done==false) {
f = frames.get(insn);
subroutine = subroutines.get(insn);
queued.put(insn, Boolean.FALSE);
finished.add(insn);
// if(state == ExecutionState.NORMAL) {
Action action = process(insn, frames);
switch(action) {
case REPLACE:
insn = insns.get(oldIndex);
// DebugUtils.println(">> F: " + f);
frames.put(insn, f);
subroutines.put(insn, subroutine);
queued.put(insn, Boolean.FALSE);
done = true;
break;
case REMOVE:
insn = insns.get(oldIndex);
frames.put(insn, f);
subroutines.put(insn, subroutine);
queued.put(insn, Boolean.FALSE);
continue;
default:
done = true;
}
// } else if(state == ExecutionState.HANDLING) {
// // executing only, pass through preprocessing
// done = true;
// }
}
AbstractInsnNode insnNode = insn;
// m.instructions.get(insn);
// DebugUtils.println(insn + ":" + insnNode);
int insnOpcode = insnNode.getOpcode();
int insnType = insnNode.getType();
if(insnOpcode != -1) {
//DebugUtils.println("Opcode: " + AbstractVisitor.OPCODES[insnOpcode]);
// DebugUtils.println("Frame: " + f);
//debug(insn);
DebugUtils.dump(insn);
}
if (insnType == AbstractInsnNode.LABEL || insnType == AbstractInsnNode.LINE || insnType == AbstractInsnNode.FRAME) {
merge(insn.getNext(), f, subroutine);
newControlFlowEdge(insn, insn.getNext());
} else {
current.init(f).execute(insnNode, interpreter);
subroutine = subroutine == null ? null : subroutine.copy();
if (insnNode instanceof JumpInsnNode) {
JumpInsnNode j = (JumpInsnNode) insnNode;
if (insnOpcode != GOTO && insnOpcode != JSR) {
merge(insn.getNext(), current, subroutine);
newControlFlowEdge(insn, insn.getNext());
}
AbstractInsnNode jump = j.label;
if (insnOpcode == JSR) {
merge(jump, current, new Subroutine(j.label, m.maxLocals, j));
} else {
merge(jump, current, subroutine);
}
newControlFlowEdge(insn, jump);
} else if (insnNode instanceof LookupSwitchInsnNode) {
LookupSwitchInsnNode lsi = (LookupSwitchInsnNode) insnNode;
AbstractInsnNode jump = lsi.dflt;
merge(jump, current, subroutine);
newControlFlowEdge(insn, jump);
for (int j = 0; j < lsi.labels.size(); ++j) {
LabelNode label = (LabelNode) lsi.labels.get(j);
jump = label;
merge(jump, current, subroutine);
newControlFlowEdge(insn, jump);
}
} else if (insnNode instanceof TableSwitchInsnNode) {
TableSwitchInsnNode tsi = (TableSwitchInsnNode) insnNode;
AbstractInsnNode jump = tsi.dflt;
merge(jump, current, subroutine);
newControlFlowEdge(insn, jump);
for (int j = 0; j < tsi.labels.size(); ++j) {
LabelNode label = (LabelNode) tsi.labels.get(j);
jump = label;
merge(jump, current, subroutine);
newControlFlowEdge(insn, jump);
}
} else if (insnOpcode == RET) {
if (subroutine == null) {
throw new AnalyzerException("RET instruction outside of a sub routine");
}
for (int i = 0; i < subroutine.callers.size(); ++i) {
Object caller = subroutine.callers.get(i);
AbstractInsnNode call = (AbstractInsnNode) caller;
if (frames.get(call) != null) {
merge(call.getNext(),
frames.get(call),
current,
subroutines.get(call),
subroutine.access);
newControlFlowEdge(insn, call.getNext());
}
}
} else if (insnOpcode != ATHROW && (insnOpcode < IRETURN || insnOpcode > RETURN)) {
if (subroutine != null) {
if (insnNode instanceof VarInsnNode) {
int var = ((VarInsnNode) insnNode).var;
subroutine.access[var] = true;
if (insnOpcode == LLOAD || insnOpcode == DLOAD
|| insnOpcode == LSTORE
|| insnOpcode == DSTORE)
{
subroutine.access[var + 1] = true;
}
} else if (insnNode instanceof IincInsnNode) {
int var = ((IincInsnNode) insnNode).var;
subroutine.access[var] = true;
}
}
merge(insn.getNext(), current, subroutine);
newControlFlowEdge(insn, insn.getNext());
}
}
List<TryCatchBlockNode> insnHandlers = handlers.get(insn);
if (insnHandlers != null) {
for (int i = 0; i < insnHandlers.size(); ++i) {
TryCatchBlockNode tcb = insnHandlers.get(i);
Type type;
if (tcb.type == null) {
type = Type.getObjectType("java/lang/Throwable");
} else {
type = Type.getObjectType(tcb.type);
}
AbstractInsnNode jump = tcb.handler;
if (newControlFlowExceptionEdge(insn, jump)) {
handler.init(f);
handler.clearStack();
handler.push(interpreter.newValue(type));
merge(jump, handler, subroutine);
}
}
}
} catch (AnalyzerException e) {
// state = ExecutionState.HANDLED;
// rollbackNode = handle(insn, frames, e);
// bookmarkNode = insn;
// if(rollbackNode == null) {
throw new AnalyzerException("Error at instruction " + insn + ": " + e.getMessage(), e);
// }
} catch (Exception e) {
throw new AnalyzerException("Error at instruction " + insn + ": " + e.getMessage(), e);
}
}
return frames;
}
protected AbstractInsnNode handle(AbstractInsnNode insn, Map<AbstractInsnNode, Frame> frames, AnalyzerException e) {
return null;
}
protected void postprocess(final AbstractInsnNode insnNode, final Interpreter interpreter) {
}
protected Action process(AbstractInsnNode s, Map<AbstractInsnNode, Frame> frames) {
return Action.NONE;
}
private void findSubroutine(AbstractInsnNode insn, final Subroutine sub, final List<AbstractInsnNode> calls)
throws AnalyzerException
{
while (true) {
if(insn == null) {
throw new AnalyzerException("Execution can fall off end of the code");
}
if(subroutines.get(insn) != null) {
return;
}
subroutines.put(insn, sub.copy());
AbstractInsnNode node = insn;
// calls findSubroutine recursively on normal successors
if (node instanceof JumpInsnNode) {
if (node.getOpcode() == JSR) {
// do not follow a JSR, it leads to another subroutine!
calls.add(node);
} else {
JumpInsnNode jnode = (JumpInsnNode) node;
findSubroutine(jnode.label, sub, calls);
}
} else if (node instanceof TableSwitchInsnNode) {
TableSwitchInsnNode tsnode = (TableSwitchInsnNode) node;
findSubroutine(tsnode.dflt, sub, calls);
for (int i = tsnode.labels.size() - 1; i >= 0; --i) {
LabelNode l = (LabelNode) tsnode.labels.get(i);
findSubroutine(l, sub, calls);
}
} else if (node instanceof LookupSwitchInsnNode) {
LookupSwitchInsnNode lsnode = (LookupSwitchInsnNode) node;
findSubroutine(lsnode.dflt, sub, calls);
for (int i = lsnode.labels.size() - 1; i >= 0; --i) {
LabelNode l = (LabelNode) lsnode.labels.get(i);
findSubroutine(l, sub, calls);
}
}
// calls findSubroutine recursively on exception handler successors
List<TryCatchBlockNode> insnHandlers = handlers.get(insn);
if (insnHandlers != null) {
for (int i = 0; i < insnHandlers.size(); ++i) {
TryCatchBlockNode tcb = insnHandlers.get(i);
findSubroutine(tcb.handler, sub, calls);
}
}
// if insn does not falls through to the next instruction, return.
switch (node.getOpcode()) {
case GOTO:
case RET:
case TABLESWITCH:
case LOOKUPSWITCH:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case RETURN:
case ATHROW:
return;
}
insn = insn.getNext();
}
}
/**
* Returns the symbolic stack frame for each instruction of the last
* recently analyzed method.
*
* @return the symbolic state of the execution stack frame at each bytecode
* instruction of the method. The size of the returned array is
* equal to the number of instructions (and labels) of the method. A
* given frame is <tt>null</tt> if the corresponding instruction
* cannot be reached, or if an error occured during the analysis of
* the method.
*/
public Map<AbstractInsnNode, Frame> getFrames() {
return frames;
}
/**
* Returns the exception handlers for the given instruction.
*
* @param insn the index of an instruction of the last recently analyzed
* method.
* @return a list of {@link TryCatchBlockNode} objects.
*/
public List<TryCatchBlockNode> getHandlers(final AbstractInsnNode insn) {
return handlers.get(insn);
}
/**
* Constructs a new frame with the given size.
*
* @param nLocals the maximum number of local variables of the frame.
* @param nStack the maximum stack size of the frame.
* @return the created frame.
*/
protected Frame newFrame(final int nLocals, final int nStack) {
return new Frame(nLocals, nStack);
}
/**
* Constructs a new frame that is identical to the given frame.
*
* @param src a frame.
* @return the created frame.
*/
protected Frame newFrame(final Frame src) {
return new Frame(src);
}
/**
* Creates a control flow graph edge. The default implementation of this
* method does nothing. It can be overriden in order to construct the
* control flow graph of a method (this method is called by the
* {@link #analyze analyze} method during its visit of the method's code).
*
* @param insn an instruction index.
* @param successor index of a successor instruction.
*/
protected void newControlFlowEdge(final AbstractInsnNode insn, final AbstractInsnNode successor) {
}
/**
* Creates a control flow graph edge corresponding to an exception handler.
* The default implementation of this method does nothing. It can be
* overriden in order to construct the control flow graph of a method (this
* method is called by the {@link #analyze analyze} method during its visit
* of the method's code).
*
* @param insn an instruction index.
* @param successor index of a successor instruction.
* @return true if this edge must be considered in the data flow analysis
* performed by this analyzer, or false otherwise. The default
* implementation of this method always returns true.
*/
protected boolean newControlFlowExceptionEdge(
final AbstractInsnNode insn,
final AbstractInsnNode successor)
{
return true;
}
// -------------------------------------------------------------------------
private void merge(
final AbstractInsnNode insn,
final Frame frame,
final Subroutine subroutine) throws AnalyzerException
{
Frame oldFrame = frames.get(insn);
Subroutine oldSubroutine = subroutines.get(insn);
boolean changes;
if (oldFrame == null) {
frames.put(insn, newFrame(frame));
changes = true;
} else {
changes = oldFrame.merge(frame, interpreter);
}
if (oldSubroutine == null) {
if (subroutine != null) {
subroutines.put(insn, subroutine.copy());
changes = true;
}
} else {
if (subroutine != null) {
changes |= oldSubroutine.merge(subroutine);
}
}
if (changes && (!queued.containsKey(insn) || queued.get(insn).equals(Boolean.FALSE))) {
queued.put(insn, Boolean.TRUE);
queue.put(top, insn);
top = top.getNext();
}
}
private void merge(
final AbstractInsnNode insn,
final Frame beforeJSR,
final Frame afterRET,
final Subroutine subroutineBeforeJSR,
final boolean[] access) throws AnalyzerException
{
Frame oldFrame = frames.get(insn);
Subroutine oldSubroutine = subroutines.get(insn);
boolean changes;
afterRET.merge(beforeJSR, access);
if (oldFrame == null) {
frames.put(insn, newFrame(afterRET));
changes = true;
} else {
changes = oldFrame.merge(afterRET, access);
}
if (oldSubroutine != null && subroutineBeforeJSR != null) {
changes |= oldSubroutine.merge(subroutineBeforeJSR);
}
if (changes && (!queued.containsKey(insn) || queued.get(insn).equals(Boolean.FALSE))) {
queued.put(insn, Boolean.TRUE);
queue.put(top, insn);
top = top.getNext();
}
}
}