/*
* This file is part of JOP, the Java Optimized Processor
* see <http://www.jopdesign.com/>
*
* Copyright (C) 2010, Stefan Hepp (stefan@stefant.org).
*
* 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;
import com.jopdesign.common.KeyManager.CustomKey;
import com.jopdesign.common.KeyManager.KeyType;
import com.jopdesign.common.bcel.StackMapTable;
import com.jopdesign.common.code.CallString;
import com.jopdesign.common.code.ControlFlowGraph;
import com.jopdesign.common.code.ControlFlowGraph.CFGNode;
import com.jopdesign.common.code.ControlFlowGraph.InvokeNode;
import com.jopdesign.common.code.InvokeSite;
import com.jopdesign.common.code.LoopBound;
import com.jopdesign.common.logger.LogConfig;
import com.jopdesign.common.misc.AppInfoError;
import com.jopdesign.common.misc.BadGraphError;
import com.jopdesign.common.misc.BadGraphException;
import com.jopdesign.common.misc.HashedString;
import com.jopdesign.common.misc.JavaClassFormatError;
import com.jopdesign.common.misc.MiscUtils;
import com.jopdesign.common.processormodel.ProcessorModel;
import com.jopdesign.common.type.FieldRef;
import com.jopdesign.common.type.MethodRef;
import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.classfile.StackMap;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.CPInstruction;
import org.apache.bcel.generic.CodeExceptionGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.FieldOrMethod;
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.InvokeInstruction;
import org.apache.bcel.generic.LineNumberGen;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.ReferenceType;
import org.apache.bcel.generic.TargetLostException;
import org.apache.bcel.generic.Type;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Stefan Hepp (stefan@stefant.org)
*/
public class MethodCode {
/**
* A key used to attach an {@link InvokeSite} object to an InstructionHandle.
*/
private static final Object KEY_INVOKESITE = new HashedString("MethodInfo.InvokeSite");
// Keys to attach custom values to InstructionHandles
private static final Object KEY_CUSTOMVALUES = new HashedString("MethodCode.CustomValues");
// Keys to attach values directly to InstructionHandles, which are not handled by KeyManager
private static final Object KEY_LINENUMBER = new HashedString("MethodCode.LineNumber");
private static final Object KEY_SOURCECLASS = new HashedString("MethodCode.SourceClass");
// We attach the LoopBounds as CustomKeys, so we can use the KeyManager to clear/copy/.. them.
// TODO we could also attach them directly to InstructionHandles to save one map access (but then make this key private!)
public static final CustomKey KEY_LOOPBOUND;
static {
KEY_LOOPBOUND = KeyManager.getSingleton().registerKey(KeyType.CODE, "MethodCode.LoopBound");
}
private static final Logger logger = Logger.getLogger(LogConfig.LOG_CODE+".MethodCode");
private final MethodInfo methodInfo;
private final MethodGen methodGen;
private ControlFlowGraph cfg;
/**
* Only to be used by MethodInfo.
*
* @param methodInfo reference to the method containing the code.
*/
MethodCode(MethodInfo methodInfo) {
this.methodInfo = methodInfo;
methodGen = methodInfo.getInternalMethodGen();
}
public AppInfo getAppInfo() {
return methodInfo.getAppInfo();
}
public ClassInfo getClassInfo() {
return methodInfo.getClassInfo();
}
public MethodInfo getMethodInfo() {
return methodInfo;
}
public ConstantPoolGen getConstantPoolGen() {
return methodInfo.getConstantPoolGen();
}
//////////////////////////////////////////////////////////////////////////////
// Various wrappers to BCEL methods
//////////////////////////////////////////////////////////////////////////////
public int getMaxStack() {
return methodGen.getMaxStack();
}
public int getMaxLocals() {
return methodGen.getMaxLocals();
}
public LocalVariableGen addLocalVariable(String name, Type type, int slot, InstructionHandle start, InstructionHandle end) {
return methodGen.addLocalVariable(name, type, slot, start, end);
}
public LocalVariableGen addLocalVariable(String name, Type type, InstructionHandle start, InstructionHandle end) {
return methodGen.addLocalVariable(name, type, start, end);
}
public void removeLocalVariable(LocalVariableGen l) {
methodGen.removeLocalVariable(l);
}
public void removeLocalVariables() {
methodGen.removeLocalVariables();
}
public LocalVariableGen[] getLocalVariables() {
return methodGen.getLocalVariables();
}
public Attribute[] getAttributes() {
return methodGen.getCodeAttributes();
}
public void addAttribute(Attribute a) {
methodGen.addCodeAttribute(a);
}
public void removeAttribute(Attribute a) {
methodGen.removeCodeAttribute(a);
}
public void removeAttributes() {
methodGen.removeCodeAttributes();
}
//////////////////////////////////////////////////////////////////////////////
// Line number handling
//////////////////////////////////////////////////////////////////////////////
/**
* Get a table of linenumber entries.
* If you want to get the line number of an instruction, use {@link #getLineNumber(InstructionHandle)} instead.
*
* @return a table of linenumber entries.
*/
public LineNumberGen[] getLineNumbers() {
return methodGen.getLineNumbers();
}
/**
* Get the linenumber table attribute.
* If you want to get the line number of an instruction, use {@link #getLineNumber(InstructionHandle)} instead.
*
* @return the linenumbers attribute.
*/
public LineNumberTable getLineNumberTable() {
return methodGen.getLineNumberTable(getConstantPoolGen());
}
/**
* Remove a line number entry.
* @param l the entry to remove.
*/
public void removeLineNumber(LineNumberGen l) {
methodGen.removeLineNumber(l);
}
/**
* Remove all line numbers.
*/
public void removeLineNumbers() {
methodGen.removeLineNumbers();
// TODO should we do something about the CFG?
for (InstructionHandle ih : methodGen.getInstructionList().getInstructionHandles()) {
ih.removeAttribute(KEY_SOURCECLASS);
ih.removeAttribute(KEY_LINENUMBER);
}
}
public void setLineNumber(InstructionHandle ih, int src_line) {
setLineNumber(ih, null, src_line);
}
/**
* Removes all line number entries from this instruction. This has the effect that the instruction
* gets the same line number as the previous instruction.
*
* @param ih the instruction to clear.
*/
public void clearLineNumber(InstructionHandle ih) {
ih.removeAttribute(KEY_SOURCECLASS);
ih.removeAttribute(KEY_LINENUMBER);
LineNumberGen entry = getLineNumberEntry(ih, false);
if (entry != null) {
removeLineNumber(entry);
}
}
public LineNumberGen getLineNumberEntry(InstructionHandle ih, boolean checkPrevious) {
InstructionHandle prev = ih;
while (prev != null) {
InstructionTargeter[] targeter = prev.getTargeters();
if (targeter != null) {
for (InstructionTargeter t : targeter) {
if (t instanceof LineNumberGen) {
// found a linenumber attached to this
return (LineNumberGen) t;
}
}
}
// no match found
if (checkPrevious && prev.getAttribute(KEY_LINENUMBER) == null) {
prev = prev.getPrev();
} else {
break;
}
}
return null;
}
/**
* Get the line number of the instruction. This may refer to a line number in another file.
* To get the correct source file for this instruction, use {@link #getSourceFileName(InstructionHandle)}.
*
* @see #getSourceFileName(InstructionHandle)
* @param ih the instruction to check.
* @return the line number of the instruction, or -1 if unknown.
*/
public int getLineNumber(InstructionHandle ih) {
InstructionHandle handle = findLineNumberHandle(ih);
if (handle == null) return -1;
Integer line = (Integer) handle.getAttribute(KEY_LINENUMBER);
if (line != null) {
return line;
}
LineNumberGen entry = getLineNumberEntry(handle, false);
return entry != null ? entry.getSourceLine() : -1;
}
public String getLineString(InstructionHandle ih) {
InstructionHandle handle = findLineNumberHandle(ih);
if (handle == null) {
return "<none>";
}
String className = getSourceClassAttribute(handle);
if (className != null) {
return className + ":" + getLineNumber(handle);
}
LineNumberGen lg = getLineNumberEntry(handle, false);
return String.valueOf( lg.getSourceLine() );
}
/**
* @param ih the *first* instruction which should be assigned to this source line.
* Use {@link #clearLineNumber(InstructionHandle)} for all following instructions which have the same line.
* @param classInfo the classinfo containing the original source code, or null to use the class of this method
* @param line the line number to set
*/
public void setLineNumber(InstructionHandle ih, ClassInfo classInfo, int line) {
LineNumberGen lg = getLineNumberEntry(ih, false);
if (classInfo == null || classInfo.equals(methodInfo.getClassInfo())) {
ih.removeAttribute(KEY_SOURCECLASS);
ih.removeAttribute(KEY_LINENUMBER);
if (lg != null) {
lg.setSourceLine(line);
} else {
methodGen.addLineNumber(ih, line);
}
} else {
// or should we attach the ClassInfo directly?
ih.addAttribute(KEY_SOURCECLASS, classInfo.getClassName());
ih.addAttribute(KEY_LINENUMBER, line);
if (lg != null) {
removeLineNumber(lg);
}
}
}
/**
* @param ih the instruction handle to check.
* @return the class info assigned to this handle or to a previous handle, default is the class info of the method.
*/
public ClassInfo getSourceClassInfo(InstructionHandle ih) {
InstructionHandle handle = findLineNumberHandle(ih);
if (handle == null) return methodInfo.getClassInfo();
String sourceClass = (String) handle.getAttribute(KEY_SOURCECLASS);
if (sourceClass != null) {
return getAppInfo().getClassInfo(sourceClass);
}
return methodInfo.getClassInfo();
}
public String getSourceFileName(InstructionHandle ih) {
ClassInfo classInfo = getSourceClassInfo(ih);
if (classInfo == null) {
return null;
}
return classInfo.getSourceFileName();
}
/**
* This does not check previous instructions and only returns something other than null if the source class name
* is set and different from this methods' class.
*
* @see #getSourceClassInfo(InstructionHandle)
* @param ih the instruction handle to check.
* @return the source class name assigned to this handle, or null if no class name is assigned to this handle.
*/
public String getSourceClassAttribute(InstructionHandle ih) {
String sourceClass = (String) ih.getAttribute(KEY_SOURCECLASS);
if (sourceClass != null && sourceClass.equals(getClassInfo().getClassName())) {
// we might actually convert this to a LineNumberGen entry..
return null;
}
return sourceClass;
}
private InstructionHandle findLineNumberHandle(InstructionHandle ih) {
InstructionHandle handle = ih;
while (handle != null) {
if (getLineNumberEntry(handle, false) != null) return handle;
if (handle.getAttribute(KEY_LINENUMBER) != null) return handle;
handle = handle.getPrev();
}
return null;
}
private void copyLineNumbers(MethodInfo sourceInfo, InstructionHandle to, InstructionHandle from) {
// TODO should we make this public?
MethodCode srcCode = sourceInfo != null ? sourceInfo.getCode() : this;
if (srcCode == null) {
throw new AppInfoError("Invalid operation: cannot copy line numbers from method without code");
}
String source = (String) from.getAttribute(KEY_SOURCECLASS);
if (source != null) {
int line = (Integer) from.getAttribute(KEY_LINENUMBER);
if (source.equals(getClassInfo().getClassName())) {
setLineNumber(to, line);
} else {
to.addAttribute(KEY_SOURCECLASS, source);
to.addAttribute(KEY_LINENUMBER, line);
}
return;
}
LineNumberGen entry = srcCode.getLineNumberEntry(from, false);
if (entry != null) {
int line = entry.getSourceLine();
source = srcCode.getClassInfo().getClassName();
if (source.equals(getClassInfo().getClassName())) {
setLineNumber(to, line);
} else {
to.addAttribute(KEY_SOURCECLASS, source);
to.addAttribute(KEY_LINENUMBER, line);
}
}
}
//////////////////////////////////////////////////////////////////////////////
// Exception handling
//////////////////////////////////////////////////////////////////////////////
/**
* Add an exception which can be thrown by this method
* @param class_name classname of the exception
*/
public void addException(String class_name) {
methodGen.addException(class_name);
}
/**
* Remove an exception which is no longer thrown by this method.
* @param c classname of the exception to remove
*/
public void removeException(String c) {
methodGen.removeException(c);
}
/**
* This method does not throw any exceptions anymore, remove all exceptions which are thrown from this method.
*/
public void removeExceptions() {
methodGen.removeExceptions();
}
/**
* @return a list of classnames of all exceptions which this method can throw
*/
public String[] getExceptions() {
return methodGen.getExceptions();
}
public CodeExceptionGen[] getExceptionHandlers() {
return methodGen.getExceptionHandlers();
}
public CodeExceptionGen addExceptionHandler(InstructionHandle start_pc, InstructionHandle end_pc, InstructionHandle handler_pc, ObjectType catch_type) {
return methodGen.addExceptionHandler(start_pc, end_pc, handler_pc, catch_type);
}
public void removeExceptionHandler(CodeExceptionGen c) {
methodGen.removeExceptionHandler(c);
}
public void removeExceptionHandlers() {
methodGen.removeExceptionHandlers();
}
public boolean isExceptionRangeStart(InstructionHandle ih) {
return !getStartingExceptionRanges(ih).isEmpty();
}
public boolean isExceptionRangeEnd(InstructionHandle ih) {
return !getEndingExceptionRanges(ih).isEmpty();
}
public boolean isExceptionRangeTarget(InstructionHandle ih) {
return !getTargetingExceptionRanges(ih).isEmpty();
}
/**
* Get a list of all exception ranges which start at this instruction.
* @param ih the instruction to check
* @return a list of all exception ranges with this instruction as start instruction, or an empty list.
*/
public List<CodeExceptionGen> getStartingExceptionRanges(InstructionHandle ih) {
List<CodeExceptionGen> list = new ArrayList<CodeExceptionGen>();
InstructionTargeter[] targeters = ih.getTargeters();
if (targeters == null) return list;
for (InstructionTargeter t : targeters) {
if (t instanceof CodeExceptionGen) {
CodeExceptionGen ceg = (CodeExceptionGen) t;
if (ceg.getStartPC().equals(ih)) {
list.add(ceg);
}
}
}
return list;
}
public List<CodeExceptionGen> getEndingExceptionRanges(InstructionHandle ih) {
List<CodeExceptionGen> list = new ArrayList<CodeExceptionGen>();
InstructionTargeter[] targeters = ih.getTargeters();
if (targeters == null) return list;
for (InstructionTargeter t : targeters) {
if (t instanceof CodeExceptionGen) {
CodeExceptionGen ceg = (CodeExceptionGen) t;
if (ceg.getEndPC().equals(ih)) {
list.add(ceg);
}
}
}
return list;
}
public List<CodeExceptionGen> getTargetingExceptionRanges(InstructionHandle ih) {
List<CodeExceptionGen> list = new ArrayList<CodeExceptionGen>();
InstructionTargeter[] targeters = ih.getTargeters();
if (targeters == null) return list;
for (InstructionTargeter t : targeters) {
if (t instanceof CodeExceptionGen) {
CodeExceptionGen ceg = (CodeExceptionGen) t;
if (ceg.getHandlerPC().equals(ih)) {
list.add(ceg);
}
}
}
return list;
}
//////////////////////////////////////////////////////////////////////////////
// Code Access, Instruction Lists and CFG
//////////////////////////////////////////////////////////////////////////////
/**
* Get the instruction list of this code. If {@link #getControlFlowGraph(boolean)} has been used before, the CFG
* will be compiled and removed first.
* <p>
* Do not call {@code dispose()} for the returned instruction list, or you will remove the instructions.
* </p>
*
* @see #compile()
* @return the instruction list of this code.
*/
public InstructionList getInstructionList() {
return getInstructionList(true, true);
}
/**
* Get the instruction list of this code.
*
* @see #compile()
* @see #removeCFG()
* @param compileCFG if true, compile an existing CFG first, else ignore any changes made to CFG.
* @param removeCFG if true, dispose the CFG of this method. If you want to modify the instruction list,
* you should set this to true to avoid inconsistencies.
* @return the instruction list of this code.
*/
public InstructionList getInstructionList(boolean compileCFG, boolean removeCFG) {
InstructionList list;
if (compileCFG) {
list = prepareInstructionList();
} else {
list = methodGen.getInstructionList();
}
if (removeCFG) {
// If one only uses the IList to analyze code but does not modify it, we could keep an existing CFG.
// Unfortunately, there is no 'const InstructionList' or 'UnmodifiableInstructionList', so we
// can never be sure what the user will do with the list, so we kill the CFG to avoid inconsistencies.
modifyCode(true);
removeCFG();
}
return list;
}
/**
* Set a new instruction list to this code.
* <p>
* IMPORTANT: If you use this method, make sure to update all targeters using
* {@link #retarget(InstructionHandle, InstructionHandle)} first, else the various tables will link
* to invalid handlers.
* </p>
* @see #getInstructionList()
* @param il the new list
*/
public void setInstructionList(InstructionList il) {
methodGen.getInstructionList().dispose();
methodGen.setInstructionList(il);
modifyCode(false);
removeCFG();
}
public InstructionHandle getInstructionHandle(int pos) {
// we do not want to trigger events here ..
InstructionList il = prepareInstructionList();
InstructionHandle ih = il.getStart();
for (int i = 0; i < pos; i++) {
ih = ih.getNext();
}
return ih;
}
/**
* Retarget all targeters (jumps, branches, exception ranges, linenumbers,..) of a handle to a new handle.
* If both the old and the new handle have a line number attached, the old line number is removed.
*
* @param oldHandle the old target
* @param newHandle the new target
*/
public void retarget(InstructionHandle oldHandle, InstructionHandle newHandle) {
InstructionTargeter[] it = oldHandle.getTargeters();
if (it == null) return;
for (InstructionTargeter targeter : it) {
if (targeter instanceof LineNumberGen) {
// check if the target already has a line number attached to it..
if (getLineNumberEntry(newHandle, false) != null) {
removeLineNumber((LineNumberGen) targeter);
}
}
targeter.updateTarget(oldHandle, newHandle);
}
}
public void retarget(TargetLostException e, InstructionHandle newTarget) {
InstructionHandle[] targets = e.getTargets();
for (InstructionHandle target : targets) {
retarget(target, newTarget);
}
}
/**
* Replace instructions in this code with an instruction list.
* If the number of instructions to replace differs from the number of source instructions, instruction
* handles will be removed or inserted appropriately and the targets will be updated.
* <p>
* The source instructions must use the constant pool of this method. Custom values will be copied.
* </p>
*
* @param replaceStart the first instruction in this code to replace
* @param replaceCount the number of instructions in this code to replace
* @param source the instructions to use as replacement.
* @return the first handle in the target list after the inserted code, or null if the last instruction in this
* list has been replaced.
*/
public InstructionHandle replace(InstructionHandle replaceStart, int replaceCount, InstructionList source) {
return replace(replaceStart, replaceCount, null, source.getStart(), source.getLength(), true);
}
/**
* Replace instructions in this code with an instruction list.
* If the number of instructions to replace differs from the number of source instructions, instruction
* handles will be removed or inserted appropriately and the targets will be updated.
* <p>
* Instruction handles will be reused, so attached values and targets will not be lost if the new length is not
* shorter than the old length. Else instruction handles are removed and the targeters to removed instructions
* are updated to the instruction after the next instruction after the deleted instructions.
* </p>
* <p>
* The source instructions must use the constant pool of this method.
* </p>
*
* @param replaceStart the first instruction in this code to replace
* @param replaceCount the number of instructions in this code to replace
* @param source the instructions to use as replacement.
* @param copyCustomKeys if true copy the custom values from the source.
* @return the first handle in the target list after the inserted code, or null if the last instruction in this
* list has been replaced.
*/
public InstructionHandle replace(InstructionHandle replaceStart, int replaceCount, InstructionList source,
boolean copyCustomKeys)
{
return replace(replaceStart, replaceCount, null, source.getStart(), source.getLength(), copyCustomKeys);
}
/**
* Replace instructions in this code with an instruction list.
* If the number of instructions to replace differs from the number of source instructions, instruction
* handles will be removed or inserted appropriately and the targets will be updated.
* <p>
* Instruction handles will be reused, so attached values and targets will not be lost if the new length is not
* shorter than the old length. Else instruction handles are removed and the targeters to removed instructions
* are updated to the instruction after the next instruction after the deleted instructions.
* </p>
*
* @param replaceStart the first instruction in this code to replace
* @param replaceCount the number of instructions in this code to replace
* @param sourceInfo the MethodInfo containing the source instruction. If non-null, the instructions will be copied
* using the constant pool from the given MethodInfo. If null, the instructions will not be copied.
* @param source the instructions to use as replacement.
* @param copyCustomKeys if true copy the custom values from the source.
* @return the first handle in the target list after the inserted code, or null if the last instruction in this
* list has been replaced.
*/
public InstructionHandle replace(InstructionHandle replaceStart, int replaceCount,
MethodInfo sourceInfo, InstructionList source, boolean copyCustomKeys)
{
return replace(replaceStart, replaceCount, sourceInfo, source.getStart(), source.getLength(), copyCustomKeys);
}
/**
* Replace instructions in this code with an instruction list or a part of it.
* If the number of instructions to replace differs from the number of source instructions, instruction
* handles will be removed or inserted appropriately and the targets will be updated.
* <p>
* Instruction handles will be reused, so attached values and targets will not be lost if the new length is not
* shorter than the old length. Else instruction handles are removed and the targeters to removed instructions
* are updated to the instruction after the next instruction after the deleted instructions.
* </p>
*
* @param replaceStart the first instruction in this code to replace
* @param replaceCount the number of instructions in this code to replace
* @param sourceInfo the MethodInfo containing the source instruction. If non-null, the instructions will be copied
* using the constant pool from the given MethodInfo. If null, the instructions will not be copied.
* @param sourceStart the first instruction in the source list to use for replacing the code.
* @param sourceCount the number of instructions to use from the source.
* @param copyCustomValues if true copy the custom values from the source.
* @return the first handle in the target list after the inserted code, or null if the last instruction in this
* list has been replaced.
*/
public InstructionHandle replace(InstructionHandle replaceStart, int replaceCount, MethodInfo sourceInfo,
InstructionHandle sourceStart, int sourceCount, boolean copyCustomValues)
{
InstructionList il = getInstructionList();
InstructionHandle current = replaceStart;
InstructionHandle currSource = sourceStart;
// update the common prefix
int cnt = Math.min(replaceCount, sourceCount);
for (int i = 0; i < cnt; i++) {
Instruction instr;
if (sourceInfo != null) {
instr = copyFrom(sourceInfo.getClassInfo(), currSource.getInstruction());
} else {
instr = currSource.getInstruction();
}
// TODO support branch instructions! need to replace the IH too
current.setInstruction(instr);
if (copyCustomValues) {
copyCustomValues(sourceInfo, current, currSource);
}
current = current.getNext();
currSource = currSource.getNext();
}
InstructionHandle next = current;
// Case 1: delete unused handles, update targets to next instruction
if (replaceCount > sourceCount) {
int rest = replaceCount - sourceCount;
for (int i=1; i < rest; i++) {
next = next.getNext();
}
InstructionHandle end = next;
next = next.getNext();
try {
// we cannot use next.getPrev, since next might be null
il.delete(current, end);
} catch (TargetLostException e) {
retarget(e, next);
}
}
// Case 2: insert new handles for rest of source
if (replaceCount < sourceCount) {
int rest = sourceCount - replaceCount;
for (int i=0; i < rest; i++) {
Instruction instr;
if (sourceInfo != null) {
instr = copyFrom(sourceInfo.getClassInfo(), currSource.getInstruction());
} else {
instr = currSource.getInstruction();
}
if (next == null) {
current = il.append(instr);
} else {
current = il.insert(next, instr);
}
if (copyCustomValues) {
copyCustomValues(sourceInfo, current, currSource);
}
currSource = currSource.getNext();
}
}
return next;
}
/**
* Create a copy of an instruction and if it uses the constantpool, copy and update the constantpool
* reference too.
* <p>Note that branch targets are not updated, so they will most likely be incorrect and need to be updated
* manually.</p> d
*
* @param sourceClass the class containing the constantpool the instruction uses.
* @param instruction the instruction to copy
* @return a new copy of the instruction.
*/
public Instruction copyFrom(ClassInfo sourceClass, Instruction instruction) {
Instruction newInstr = instruction.copy();
if (instruction instanceof CPInstruction) {
int oldIndex = ((CPInstruction)instruction).getIndex();
int newIndex = getClassInfo().addConstantInfo(sourceClass.getConstantInfo(oldIndex));
((CPInstruction)newInstr).setIndex(newIndex);
}
return newInstr;
}
public boolean isInvokeSite(InstructionHandle ih) {
return ih.getInstruction() instanceof InvokeInstruction ||
getAppInfo().getProcessorModel().isImplementedInJava(methodInfo, ih.getInstruction());
}
/**
* Get the InvokeSite for an instruction handle from the code of this method.
* This does not check if the given instruction is an invoke instruction.
*
* @param ih an instruction handle from this code
* @return the InvokeSite associated with this instruction or a new one.
*/
public InvokeSite getInvokeSite(InstructionHandle ih) {
InvokeSite is = (InvokeSite) ih.getAttribute(KEY_INVOKESITE);
if (is == null) {
is = new InvokeSite(ih, this.getMethodInfo());
ih.addAttribute(KEY_INVOKESITE, is);
}
return is;
}
/**
* Get a list of invoke sites in this method code. This also returns invoke sites for special
* instructions implemented in java.
*
* @return a list of all invoke sites in this code.
*/
public Set<InvokeSite> getInvokeSites() {
Set<InvokeSite> invokes = new LinkedHashSet<InvokeSite>();
if (hasCFG()) {
for (CFGNode node : cfg.vertexSet()) {
if (node instanceof InvokeNode) {
invokes.add( ((InvokeNode)node).getInvokeSite() );
}
}
} else {
for (InstructionHandle ih : methodGen.getInstructionList().getInstructionHandles()) {
if (isInvokeSite(ih)) {
invokes.add( getInvokeSite(ih) );
}
}
}
return invokes;
}
public MethodRef getInvokeeRef(InstructionHandle ih) {
return getInvokeSite(ih).getInvokeeRef();
}
public FieldRef getFieldRef(InstructionHandle ih) {
return getFieldRef((FieldInstruction) ih.getInstruction());
}
public FieldRef getFieldRef(FieldInstruction instr) {
ConstantPoolGen cpg = getConstantPoolGen();
String classname = getReferencedClassName(instr);
return getAppInfo().getFieldRef(classname, instr.getFieldName(cpg));
}
/**
* Get the classname or array-type name referenced by an invoke- or field instruction in this code.
*
* @param instr the instruction to check, using this methods constantpool.
* @return the referenced classname or array-typename, to be used for a ClassRef or AppInfo getter.
*/
public String getReferencedClassName(FieldOrMethod instr) {
ConstantPoolGen cpg = getConstantPoolGen();
ReferenceType refType = instr.getReferenceType(cpg);
String classname;
if (refType instanceof ObjectType) {
classname = ((ObjectType)refType).getClassName();
} else if (refType instanceof ArrayType) {
// need to call array.<method>, which class should we use? Let's decide later..
String msg = "Calling a method of an array: " +
refType.getSignature()+"#"+ instr.getName(cpg) + " in "+methodInfo;
logger.debug(msg);
classname = refType.getSignature();
} else {
// Hu??
throw new JavaClassFormatError("Unknown reference type " +refType);
}
return classname;
}
public void removeNOPs() {
prepareInstructionList();
methodGen.removeNOPs();
}
/**
* Remove attributes related to debugging (e.g. variable name mappings, stack maps, ..)
* except the linenumber table.
*/
public void removeDebugAttributes() {
removeLocalVariables();
for (Attribute a : getAttributes()) {
if (a instanceof StackMapTable ||
a instanceof StackMap)
{
removeAttribute(a);
continue;
}
// Quick hack, just remove it (not yet supported by framework, we have no type for this attribute)
ConstantUtf8 name = (ConstantUtf8) getConstantPoolGen().getConstant(a.getNameIndex());
if ("LocalVariableTypeTable".equals(name.getBytes())) {
removeAttribute(a);
}
// TODO also remove stuff like Annotations,..?
}
}
/**
* Get the control flow graph associated with this method code or create a new one.
* <p>
* By default, changes to the returned CFG are compiled back before the InstructionList of this method is accessed.
* If you want a CFG where changes to it are not compiled back automatically, use {@code new ControlFlowGraph(MethodInfo)}
* instead. Also if you want to construct a CFG for a specific context or with a different implementation finder,
* you need to construct a callgraph yourself, keep a reference to it as long as you want to keep modifications to the
* graph and you need ensure that changes to a graph invalidate other graphs of the same method yourself, if required.
* </p>
* @param clean if true, compile and recreate the graph if {@link ControlFlowGraph#isClean()} returns false.
* @return the CFG for this method.
*/
public ControlFlowGraph getControlFlowGraph(boolean clean) {
// clean/isClean could be made more general (like the CallGraphConfiguration)
if ( cfg != null && clean && !cfg.isClean()) {
cfg.compile();
cfg = null;
}
if ( this.cfg == null ) {
try {
cfg = new ControlFlowGraph(this.getMethodInfo());
// TODO we do this for now by default for the 'main' CFG on creation
cfg.registerHandleNodes();
for (AppEventHandler ah : AppInfo.getSingleton().getEventHandlers()) {
ah.onCreateMethodControlFlowGraph(cfg, clean);
}
} catch (BadGraphException e) {
throw new BadGraphError("Unable to create CFG for " + methodInfo, e);
}
}
return cfg;
}
public boolean hasCFG() {
return cfg != null;
}
/**
* Remove the CFG linked to this MethodCode, dismissing all changes made to it.
*/
public void removeCFG() {
if (cfg != null) {
cfg.dispose();
cfg = null;
}
}
/**
* Compile all changes, and update maxStack, maxLocals and positions.
*/
public void compile() {
InstructionList il = prepareInstructionList();
il.setPositions();
methodGen.setMaxLocals();
methodGen.setMaxStack();
}
/**
* @see ProcessorModel#getNumberOfBytes(MethodInfo, Instruction)
* @param il the instruction list to get the size for
* @return the number of bytes for a given instruction list on the target.
*/
public int getNumberOfBytes(InstructionList il) {
int sum = 0;
ProcessorModel pm = getAppInfo().getProcessorModel();
for (InstructionHandle ih : il.getInstructionHandles()) {
sum += pm.getNumberOfBytes(methodInfo, ih.getInstruction());
}
return sum;
}
/**
* Get the number of bytes of the code for the target architecture.
* @see #getNumberOfBytes(boolean)
* @see ControlFlowGraph#getNumberOfBytes()
* @return the number of bytes of the code.
*/
public int getNumberOfBytes() {
return getNumberOfBytes(true);
}
/**
* Get the length of the code attribute. This needs to compile the CFG first.
* @see Code#length
* @see ControlFlowGraph#getNumberOfBytes()
* @param targetSize if true, use the processor model to get the code size. If false, the
* control flow graph needs to be compiled first.
* @return the number of bytes of the code
*/
public int getNumberOfBytes(boolean targetSize) {
if (cfg == null || !targetSize) {
InstructionList il = prepareInstructionList();
if (!targetSize) {
return methodGen.getMethod().getCode().getCode().length;
} else {
return getNumberOfBytes(il);
}
} else {
return cfg.getNumberOfBytes();
}
}
public int getNumberOfWords() {
return MiscUtils.bytesToWords(getNumberOfBytes());
}
//////////////////////////////////////////////////////////////////////////////
// LoopBound methods
//////////////////////////////////////////////////////////////////////////////
/**
* Get the loopbound set to an instruction.
* <p>
* Loopbounds should be set to the last instruction of the loop head basic block
* </p>
*
* @param ih the instruction handle to check.
* @return the loopbound set for this instruction, or null if none has been set.
*/
public LoopBound getLoopBound(InstructionHandle ih) {
return (LoopBound) getCustomValue(ih, KEY_LOOPBOUND);
}
/**
* Set a new loopbound. Overwrites the existing loopbound.
* <p>
* Loopbounds should be set to the last instruction of the loop head basic block
* </p>
*
* @see #updateLoopBound(InstructionHandle, LoopBound)
* @see LoopBound#boundedAbove(long)
* @param ih the instruction handle to update.
* @param loopBound the new loopbound to set, or null to clear it.
*/
public void setLoopBound(InstructionHandle ih, LoopBound loopBound) {
setCustomValue(ih, KEY_LOOPBOUND, loopBound);
// Note: we do not return the old loopbound so that there is no confusion with updateLoopBound()
}
public LoopBound updateLoopBound(InstructionHandle ih, long upperBound) {
if (upperBound < 0) {
return getLoopBound(ih);
}
return updateLoopBound(ih, LoopBound.boundedAbove(upperBound));
}
/**
* Tighten the existing loopbound with the given bound.
* <p>
* Loopbounds should be set to the last instruction of the loop head basic block
* </p>
*
* @param ih the instruction to update.
* @param loopBound the loopbound used to tighten the existing one (can be null)
* @return the new loopbound
*/
public LoopBound updateLoopBound(InstructionHandle ih, LoopBound loopBound) {
LoopBound oldLb = getLoopBound(ih);
if (loopBound == null) {
// nothing to update
return oldLb;
}
if (oldLb == null) {
// no old value, use new value
setLoopBound(ih, loopBound);
return loopBound;
}
// TODO tighten the old bound with the new bound
// TODO we might want to know where the loopbounds came from so that we can generate messages
// if the new loopbound is larger/smaller than the old one, so maybe we want to store the origin in LoopBound
throw new AppInfoError("Implement me ..");
}
//////////////////////////////////////////////////////////////////////////////
// Get and set CustomValues
//////////////////////////////////////////////////////////////////////////////
public Object setCustomValue(InstructionHandle ih, CustomKey key, Object value) {
if (value == null) {
return clearCustomKey(ih, key);
}
@SuppressWarnings({"unchecked"})
Map<CustomKey,Object> map = (Map<CustomKey, Object>) ih.getAttribute(KEY_CUSTOMVALUES);
if (map == null) {
map = new LinkedHashMap<CustomKey, Object>(1);
ih.addAttribute(KEY_CUSTOMVALUES, map);
}
return map.put(key, value);
}
public Object getCustomValue(InstructionHandle ih, CustomKey key) {
@SuppressWarnings({"unchecked"})
Map<CustomKey,Object> map = (Map<CustomKey, Object>) ih.getAttribute(KEY_CUSTOMVALUES);
if (map == null) {
return null;
}
return map.get(key);
}
public Object setCustomValue(InstructionHandle ih, CustomKey key, CallString context, Object value) {
// TODO implement
throw new AppInfoError("Not yet implemented.");
}
public Object getCustomValue(InstructionHandle ih, CustomKey key, CallString context, boolean checkSuffixes) {
// TODO implement
throw new AppInfoError("Not yet implemented.");
}
public Object clearCustomKey(InstructionHandle ih, CustomKey key) {
@SuppressWarnings({"unchecked"})
Map<CustomKey,Object> map = (Map<CustomKey, Object>) ih.getAttribute(KEY_CUSTOMVALUES);
if (map == null) {
return null;
}
Object value = map.remove(key);
if (map.size() == 0) {
ih.removeAttribute(KEY_CUSTOMVALUES);
}
return value;
}
/**
* Copy custom values and line numbers from one instruction to an instruction in this method.
* Source and target method are used to update line number entries correctly. CustomKeys are copied using
* shallow copy.
*
* @param sourceInfo the method containing the source handle. If null assume it is the same method as the target.
* @param to the target instruction.
* @param from the source instruction.
*/
public void copyCustomValues(MethodInfo sourceInfo, InstructionHandle to, InstructionHandle from) {
@SuppressWarnings({"unchecked"})
Map<CustomKey,Object> map = (Map<CustomKey, Object>) from.getAttribute(KEY_CUSTOMVALUES);
if (map == null) {
to.removeAttribute(KEY_CUSTOMVALUES);
} else {
Map<CustomKey,Object> newMap = new LinkedHashMap<CustomKey, Object>(map);
to.addAttribute(KEY_CUSTOMVALUES, newMap);
}
copyLineNumbers(sourceInfo, to, from);
}
//////////////////////////////////////////////////////////////////////////////
// Private area. For staff only..
//////////////////////////////////////////////////////////////////////////////
private InstructionList prepareInstructionList() {
if ( cfg != null ) {
cfg.compile();
}
return methodGen.getInstructionList();
}
private void modifyCode(boolean beforeModify) {
for (AppEventHandler e : AppInfo.getSingleton().getEventHandlers()) {
e.onMethodCodeModify(this, beforeModify);
}
}
}