/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2011, Stefan Hepp (stefan@stefant.org).
* Copyright (C) 2008, Wolfgang Puffitsch
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jopdesign.common.code;
import com.jopdesign.common.KeyManager;
import com.jopdesign.common.KeyManager.CustomKey;
import com.jopdesign.common.MethodInfo;
import com.jopdesign.common.misc.JavaClassFormatError;
import org.apache.bcel.generic.ATHROW;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.CodeExceptionGen;
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.JSR;
import org.apache.bcel.generic.JSR_W;
import org.apache.bcel.generic.NOP;
import org.apache.bcel.generic.RET;
import org.apache.bcel.generic.ReturnInstruction;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.UnconditionalBranch;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* This is basically the same as the Interpreter of the DFA, except that we do not use a flow graph
* and that a ContextMap is not supported by the interpreter itself (instead it must be handled by the analysis).
*
* TODO maybe merge those two implementations
*
* @author Stefan Hepp (stefan@stefant.org)
* @author Wolfgang Puffitsch
*/
public class InstructionInterpreter<T> {
public enum EdgeType { NORMAL_EDGE, TRUE_EDGE, FALSE_EDGE, EXIT_EDGE }
public static class Edge {
private InstructionHandle tail, head;
private EdgeType type;
public Edge(InstructionHandle tail, InstructionHandle head, EdgeType type) {
this.tail = tail;
this.head = head;
this.type = type;
}
public InstructionHandle getTail() {
return tail;
}
public InstructionHandle getHead() {
return head;
}
public EdgeType getType() {
return type;
}
@Override
public int hashCode() {
return 31 * tail.hashCode() + head.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Edge)) return false;
Edge e = (Edge) obj;
return tail.equals(e.getTail()) && head.equals(e.getHead());
}
}
/**
* This key is used to attach a NOP instruction handle to each method which is used as handle for
* the result state of a method.
*/
private static final CustomKey KEY_NOP = KeyManager.getSingleton().registerStructKey("InstructionInterpreter.NOP");
private final MethodInfo methodInfo;
private final InstructionAnalysis<T> analysis;
private final Map<InstructionHandle, T> results;
private boolean startAtExceptionHandlers = false;
public InstructionInterpreter(MethodInfo methodInfo, InstructionAnalysis<T> analysis) {
this.methodInfo = methodInfo;
this.analysis = analysis;
results = new HashMap<InstructionHandle, T>();
}
public MethodInfo getMethodInfo() {
return methodInfo;
}
public boolean doStartAtExceptionHandlers() {
return startAtExceptionHandlers;
}
public void setStartAtExceptionHandlers(boolean startAtExceptionHandlers) {
this.startAtExceptionHandlers = startAtExceptionHandlers;
}
public void reset(InstructionHandle from, InstructionHandle to) {
InstructionHandle ih = from;
while (ih != to) {
// we remove the entry, initial value will be set by interpret()
results.remove(ih);
ih = ih.getNext();
}
results.put(ih, analysis.bottom());
}
public void interpret(boolean initialize) {
InstructionList il = methodInfo.getCode().getInstructionList(true, false);
InstructionHandle entry = il.getStart();
Map<InstructionHandle,T> start = new HashMap<InstructionHandle, T>();
start.put(entry, initialize ? analysis.initial(entry) : analysis.bottom());
// start at exception handler entries too?
if (startAtExceptionHandlers) {
for (CodeExceptionGen eg : methodInfo.getCode().getExceptionHandlers()) {
InstructionHandle ih = eg.getHandlerPC();
start.put(ih, initialize ? analysis.initial(eg) : analysis.bottom());
}
}
interpret(il, start, initialize);
}
public void interpret(InstructionHandle entry, boolean initialize) {
// TODO we could use the CFG instead if it exists ?
InstructionList il = methodInfo.getCode().getInstructionList(true, false);
Map<InstructionHandle,T> start = new HashMap<InstructionHandle, T>(1);
start.put(entry, initialize ? analysis.initial(entry) : analysis.bottom());
interpret(il, start, initialize);
}
private void interpret(InstructionList il, Map<InstructionHandle, T> start, boolean initialize) {
if (initialize) {
InstructionHandle ih = il.getStart();
// set initial value for all instructions
while (ih != null) {
results.put(ih, analysis.bottom());
ih = ih.getNext();
}
results.putAll(start);
} else {
// Set initial values for new instructions
for (InstructionHandle ih = il.getStart(); ih != null; ih = ih.getNext()) {
if (results.containsKey(ih)) continue;
if (ih.getPrev() == null) {
// entry instruction, must always be merged with the initial state once, even
// if this has an ingoing edge
results.put(ih, analysis.initial(ih));
} else {
results.put(ih, analysis.bottom());
// check for exception handler entry
if (ih.hasTargeters()) {
for (InstructionTargeter targeter : ih.getTargeters()) {
if (targeter instanceof CodeExceptionGen &&
((CodeExceptionGen)targeter).getHandlerPC().equals(ih))
{
results.put(ih, analysis.initial((CodeExceptionGen) targeter));
break;
}
}
}
}
}
}
LinkedList<Edge> worklist = new LinkedList<Edge>();
// setup the worklist
for (InstructionHandle ih : start.keySet()) {
if (initialize) {
// when initializing, we start from the initial values, not from ingoing edges
worklist.addAll(getOutEdges(ih));
} else {
// we continue from existing results
List<Edge> inEdges = getInEdges(il, ih);
if (inEdges.isEmpty()) {
// entry instruction without ingoing edges, nothing to continue from
worklist.addAll(getOutEdges(ih));
} else {
worklist.addAll(inEdges);
}
}
}
while (!worklist.isEmpty()) {
Edge edge = worklist.removeFirst();
InstructionHandle tail = edge.getTail();
InstructionHandle head = edge.getHead();
T tailValue = results.get(tail);
T headValue = results.get(head);
T transferred = analysis.transfer(tailValue, edge);
if (!analysis.compare(transferred, headValue)) {
T newValue = analysis.join(transferred, headValue);
results.put(head, newValue);
if (edge.getType() == EdgeType.EXIT_EDGE) {
continue;
}
List<Edge> outEdges = getOutEdges(head);
for (Edge outEdge : outEdges) {
if (worklist.contains(outEdge)) {
continue;
}
if (outEdges.size() > 1) {
worklist.addLast(outEdge);
} else {
worklist.addFirst(outEdge);
}
}
}
}
}
public T getResult(InstructionHandle ih) {
return results.get(ih);
}
public Map<InstructionHandle,T> getResults() {
return results;
}
public InstructionHandle getExitInstruction() {
InstructionHandle exit = (InstructionHandle) methodInfo.getCustomValue(KEY_NOP);
if (exit == null) {
exit = new InstructionList().append(new NOP());
methodInfo.setCustomValue(KEY_NOP, exit);
}
return exit;
}
private List<Edge> getOutEdges(InstructionHandle ih) {
List<Edge> edges = new LinkedList<Edge>();
Instruction instr = ih.getInstruction();
if (instr instanceof BranchInstruction) {
if (instr instanceof Select) {
Select s = (Select) instr;
InstructionHandle[] target = s.getTargets();
for (InstructionHandle aTarget : target) {
edges.add(new Edge(ih, aTarget, EdgeType.TRUE_EDGE));
}
edges.add(new Edge(ih, s.getTarget(), EdgeType.FALSE_EDGE));
} else {
BranchInstruction b = (BranchInstruction) instr;
edges.add(new Edge(ih, b.getTarget(), EdgeType.TRUE_EDGE));
}
}
// Check if we can fall through to the next instruction
if (ih.getNext() != null
&& !(instr instanceof UnconditionalBranch
|| instr instanceof Select || instr instanceof ReturnInstruction))
{
if (instr instanceof BranchInstruction) {
edges.add(new Edge(ih, ih.getNext(), EdgeType.FALSE_EDGE));
} else {
edges.add(new Edge(ih, ih.getNext(), EdgeType.NORMAL_EDGE));
}
}
if (instr instanceof ReturnInstruction) {
edges.add(new Edge(ih, getExitInstruction(), EdgeType.EXIT_EDGE));
}
if (instr instanceof ATHROW) {
// TODO should we handle this somehow? Insert edges to the exception handlers or to an return-by-exception
// exit instruction?
// for now, just ignore them
}
// TODO handle JSR (jump to subroutine, continue after JSR) and RET (tricky.. jump back to corresponding JSR)
// but for now, we just ignore them too.. in a safe way :)
if (instr instanceof RET || instr instanceof JSR || instr instanceof JSR_W) {
throw new JavaClassFormatError("Unsupported instruction "+instr+" in "+methodInfo);
}
return edges;
}
private List<Edge> getInEdges(InstructionList il, InstructionHandle ih) {
List<Edge> edges = new LinkedList<Edge>();
InstructionHandle prev = ih.getPrev();
if (prev != null) {
// check if we can fall through from prev instruction
Instruction instr = prev.getInstruction();
if (!(instr instanceof UnconditionalBranch
|| instr instanceof Select || instr instanceof ReturnInstruction))
{
if (instr instanceof BranchInstruction) {
edges.add(new Edge(prev, ih, EdgeType.FALSE_EDGE));
} else {
edges.add(new Edge(prev, ih, EdgeType.NORMAL_EDGE));
}
}
}
// This is bad: because we do not get the instruction handles of the targeters, we need to search the
// whole instruction list. There is no other way to find the ingoing edges except than constructing the
// flow graph explicitly
for (InstructionHandle targeter = il.getStart(); targeter != null; targeter = targeter.getNext()) {
// TODO we do not care about CodeExceptionGen targeters.. or should we? We should set the initial value
// for the instruction for the exception handler
if (!(targeter.getInstruction() instanceof BranchInstruction)) continue;
BranchInstruction bi = (BranchInstruction) targeter.getInstruction();
if (bi.containsTarget(ih)) {
edges.add(new Edge(targeter, ih, EdgeType.TRUE_EDGE));
}
}
return edges;
}
}