package alt.jiapi.util;
import java.lang.reflect.Modifier;
import alt.jiapi.InstrumentationDescriptor;
import alt.jiapi.InstrumentationException;
import alt.jiapi.MethodInstrumentor;
import alt.jiapi.Rule;
import alt.jiapi.reflect.BranchInstruction;
import alt.jiapi.reflect.Instruction;
import alt.jiapi.reflect.InstructionFactory;
import alt.jiapi.reflect.InstructionList;
import alt.jiapi.reflect.JiapiClass;
import alt.jiapi.reflect.JiapiMethod;
import alt.jiapi.reflect.Loader;
import alt.jiapi.reflect.Signature;
import alt.jiapi.reflect.instruction.Invocation;
import alt.jiapi.reflect.instruction.OpcodeGroups;
import alt.jiapi.reflect.instruction.Opcodes;
import org.apache.log4j.Category;
/**
* HotSpotAdvisor is used to copy instructions from
* HotSpotAdvice to class being instrumented. This makes
* it possible to instrument target classes without any
* knowledge about Java bytecodes or instruction set.<p>
*
* Conseptually, this class, with the aid of HotSpotAdvise,
* provides the same thing as
* <a href="http://eclipse.org/aspectj/">AspectJ</a>s'
* join-point, before advice, after advice and around advice.<p>
*
* At some point in time, we might add point-cut semantics
* here.
* At the moment, point-cut semantics can be simulated to some extent with
* the aid of InstrumentationDescriptor<p>
*
* At the moment there is not such thing as HotSpotContext.
* For example, HotSpotAdvice knows, that an invocation is being made,
* but it does not know what are the call parameters of the invocation,
* and it does not know what the invocation returned.<p>
*
* Future versions of HotSpotAdvice might have this knowledge.
*
* @see HotSpotAdvice
* @see HotSpot for the definition of hotspot
*/
public class HotSpotAdvisor {
private static Category log = Category.getInstance(HotSpotAdvisor.class);
/**
* This field can be used in constructor to search for any
* invocations being made. Note, that a call to super classes'
* constructor, is technically just an invocation. If you like
* to exclude calls to super class constructor, define your own
* byte array like this:
* <pre>
* byte[] invocations = new byte[] {
* Opcodes.INVOKESTATIC,
* Opcodes.INVOKEVIRTUAL,
* Opcodes.INVOKEINTERFACE
* };
* </pre>
*/
public static final byte[] INVOCATIONS = OpcodeGroups.INVOKE_INSTRUCTIONS;
/**
* This field can be used in constructor to search for hotspots,
* that makes a method return normally.
*/
public static final byte[] RETURNS = OpcodeGroups.RETURN_INSTRUCTIONS;
/**
* This field can be used in constructor to search for hotspots,
* that do some sort of field access. Like get or set field.
*/
public static final byte[] FIELD_ACCESSES = OpcodeGroups.FIELD_ACCESS_INSTRUCTIONS;
/**
* Creates new HotSpotAdvisor.
*
* @param id InstrumentationDescriptor to use. A Special
* instruction copy Instrumentor is added to this
* descriptor, so users should only add inclusion/exclusion
* rules to descriptor.
* @param advice A HotSpotAdvice where instructions to be copied are
* taken.
* @param hotSpot this byte represents an opcode
* of the hotspot.
* @see alt.jiapi.reflect.instruction.Opcodes
*/
public HotSpotAdvisor(InstrumentationDescriptor id,
HotSpotAdvice advice,
byte hotSpot) {
this(id, advice, new byte[] {hotSpot});
}
/**
* Creates new HotSpotAdvisor.
*
* @param id InstrumentationDescriptor to use. A Special
* instruction copy Instrumentor is added to this
* descriptor, so users should only add inclusion/exclusion
* rules to descriptor.
* @param advice A HotSpotAdvice where instructions to be copied are
* taken.
* @param hotSpots an array of bytes. Each byte represents an opcode
* of the hotspot. So, multiple opcodes may be used.
*
* @see #INVOCATIONS
* @see #RETURNS
* @see #FIELD_ACCESSES
* @see alt.jiapi.reflect.instruction.Opcodes
* @see alt.jiapi.reflect.instruction.OpcodeGroups
*/
public HotSpotAdvisor(InstrumentationDescriptor id,
HotSpotAdvice advice,
byte[] hotSpots) {
this(id, advice, hotSpots, "*");
}
/**
* Creates new HotSpotAdvisor.
*
* @param id InstrumentationDescriptor to use. A Special
* instruction copy Instrumentor is added to this
* descriptor, so users should only add inclusion/exclusion
* rules to descriptor.
* @param advice A HotSpotAdvice where instructions to be copied are
* taken.
* @param hotSpots an array of bytes. Each byte represents an opcode
* of the hotspot. So, multiple opcodes may be used.
*
* @see #INVOCATIONS
* @see #RETURNS
* @see #FIELD_ACCESSES
* @see alt.jiapi.reflect.instruction.Opcodes
* @see alt.jiapi.reflect.instruction.OpcodeGroups
*/
public HotSpotAdvisor(InstrumentationDescriptor id,
HotSpotAdvice advice,
byte[] hotSpots, String resolution) {
try {
// Do we need this. In my testing, advice got instrumented
// by accident by include=samples* rule.
// System.out.println("Adding exclusion rule '" +
// advice.getClass().getName() + "'");
id.addExclusionRule(advice.getClass().getName());
}
catch(Exception e) {
}
id.addInstrumentor(new HSInstrumentor(advice, hotSpots, resolution));
}
/**
* This private Instrucmentor makes the actual bytecode
* copy procedure.
*/
private class HSInstrumentor extends MethodInstrumentor {
private byte[] hotSpots;
private JiapiMethod adviceMethod;
private Rule rule;
HSInstrumentor(HotSpotAdvice advice, byte[] hotSpots, String resolution) {
try {
this.rule = new Rule(resolution);
Loader l = new Loader();
log.debug("Loading advice " + advice.getClass().getName());
JiapiClass clazz = l.loadClass(advice.getClass().getName());
log.debug("Getting advice() method ");
this.adviceMethod =
clazz.getDeclaredMethod("advice", new String[0]);
this.hotSpots = hotSpots;
}
catch(NoSuchMethodException nsme) {
// Should not happen, since it is an abstract method and
// there has to be an implementation for it.
log.error("Internal error: Could not find HotSpotAdvice.advice() method", nsme);
throw new InstrumentationException("Could not find method advice() from " + advice.getClass() + ": " + nsme);
}
catch(ClassNotFoundException cnfe) {
// Should not happen, can happen if Advice was loaded
// from a place other than classpath
log.error("Internal error: Could load class HotSpotAdvice", cnfe);
throw new InstrumentationException("Could not load class " + advice.getClass() + ": " + cnfe);
}
catch(java.io.IOException ioe) {
// Should not happen
log.error("Internal error: Could load class HotSpotAdvice", ioe);
throw new InstrumentationException("Could not load class " + advice.getClass() + ": " + ioe);
}
catch(Exception e) {
log.error("Failed to initialize HotSpotAdvisor", e);
throw new InstrumentationException("Failed to initialize HotSpotAdvisor: " + e);
}
}
/**
* Instrument given method. Find hotspots, and copy instructions
* from advice before/after/around hotspot
*/
public void instrument(JiapiMethod m) throws InstrumentationException {
log.debug("Instrumenting " + m.getDeclaringClass().getName() +
"#" + m);
HotSpotLocator hsl =
new HotSpotLocator(m.getInstructionList(), hotSpots);
HotSpot[] hsa = hsl.getHotSpots();
for (int i = 0; i < hsa.length; i++) {
HotSpot hs = hsa[i];
if (rule.match(hs.getName())) {
copyInstructions(hs);
}
}
}
/**
* Actual copying of instructions to hotspot.
*/
private void copyInstructions(HotSpot hs) {
InstructionList am_il = adviceMethod.getInstructionList();
InstructionList il = hs.getInstructionList();
Instruction firstHotSpotInstruction = il.get(0);
//
// NOTE: We must handle local variables correctly
// NOTE: should be done in InstructionList.add/insert(Instruction)
am_il = changeLocalVars(am_il, il.getDeclaringMethod().getMaxLocals()-1);
int idx = indexOfDoHotSpot();
if (idx == -1) {
log.debug("Replacing hotspot with advice-method");
il.clear();
il.add(am_il);
return;
}
else {
InstructionList before = am_il.createView(0, idx - 1);
// If first hs instruction is branch target, we
// will have to change this to first instruction of
// before-list, so that we do not introduce dead-code.
if (before.size() > 0) {
checkForBranchTarget(firstHotSpotInstruction, il.getDeclaringMethod().getInstructionList(), before.get(0));
}
InstructionList after = null;
if (idx+1 < am_il.size()) {
// Strip 'return' statement
after = am_il.createView(idx + 1, am_il.size() - 1);
}
//System.out.println("il before change " + il);
il.insert(0, before);
if (after != null) {
il.add(after);
}
}
// process advice list
processAdviceTriggers(il, hs);
}
/**
* Finds <b>first</b> occurense of a call to doHotSpot();
* If not found, return -1.
*
* Is it a bug, if there are more occurences of doHotSpot()
* calls in Advice. And if it is a bug, is it a bug in
* this class(not taking this into account), or Advice
* (being rogue Advice). Should we enable this???
*
* Anyway, at the moment, instrumented class will not pass bytecode
* verifier(I think), since there will be a non-existent call to
* super.doHotSpot().
*/
private int indexOfDoHotSpot() {
InstructionList il = adviceMethod.getInstructionList();
HotSpotLocator hsl = new HotSpotLocator(il, Opcodes.INVOKEVIRTUAL);
HotSpot[] dohsCandidates = hsl.getHotSpots();
for (int i = 0; i < dohsCandidates.length; i++) {
Invocation inv =
(Invocation)dohsCandidates[i].getHotSpotInstruction();
if (!inv.getClassName().equals(adviceMethod.getDeclaringClass().getName())) {
// Process only calls to HotSpotAdvice class
continue;
}
if ("doHotSpot".equals(inv.getMethodName())) {
// if (inv.getAttribute("synthetic") != null) {
// // Found it
// return il.indexOf(inv);
// }
// continue;
return il.indexOf(inv);
}
}
return -1;
}
/**
* Change usage of local variables in target InstructionList so,
* that it will not overlap with 'other' list.
*
* If Advice declares local variables, they start from index 0,
* but so do local vars in target class. So this method changes
* local variable accesses in advice to start from
* <target.getMaxLocals()>
*
* @param advice InstructionList to change
* @param maxLocalsInOtherList Max-Locals in other list
*/
private InstructionList changeLocalVars(InstructionList advice,
int maxLocalsInOtherList) {
// if (maxLocalsInOtherList == 0) {
// // Nothing to do, other list does not contain
// // local variables
// return advice;
// }
// BUG: These should not be needed. For some reason,
// JiapiMethod.getMaxLocals() returns wrong values?
// Commenting these out has no harm other than
// missing unused local variable slots
maxLocalsInOtherList--;
// following could be added, if no longs/doubles are in target
// list; they reserve two slots for local vars. Another
// silly stuff for longs/doubles in JVMs
// maxLocalsInOtherList--;
InstructionList newList = new InstructionList();
InstructionFactory factory = new InstructionFactory();
for(int i = 0; i < advice.size(); i++) {
Instruction ins = advice.get(i);
newList.add(ins);
switch(ins.getOpcode()) {
// -- ALOAD family --------------------------------------
case Opcodes.ALOAD_0:
newList.replace(i, factory.aload(maxLocalsInOtherList));
break;
case Opcodes.ALOAD_1:
newList.replace(i, factory.aload(maxLocalsInOtherList + 1));
break;
case Opcodes.ALOAD_2:
newList.replace(i, factory.aload(maxLocalsInOtherList + 2));
break;
case Opcodes.ALOAD_3:
newList.replace(i, factory.aload(maxLocalsInOtherList + 3));
break;
case Opcodes.ALOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
case Opcodes.ASTORE_0:
newList.replace(i, factory.astore(maxLocalsInOtherList));
break;
case Opcodes.ASTORE_1:
newList.replace(i, factory.astore(maxLocalsInOtherList + 1));
break;
case Opcodes.ASTORE_2:
newList.replace(i, factory.astore(maxLocalsInOtherList + 2));
break;
case Opcodes.ASTORE_3:
newList.replace(i, factory.astore(maxLocalsInOtherList + 3));
break;
case Opcodes.ASTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
// -- ILOAD family --------------------------------------
case Opcodes.ILOAD_0:
newList.replace(i, factory.iload(maxLocalsInOtherList));
break;
case Opcodes.ILOAD_1:
newList.replace(i, factory.iload(maxLocalsInOtherList + 1));
break;
case Opcodes.ILOAD_2:
newList.replace(i, factory.iload(maxLocalsInOtherList + 2));
break;
case Opcodes.ILOAD_3:
newList.replace(i, factory.iload(maxLocalsInOtherList + 3));
break;
case Opcodes.ILOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
case Opcodes.ISTORE_0:
newList.replace(i, factory.istore(maxLocalsInOtherList));
break;
case Opcodes.ISTORE_1:
newList.replace(i, factory.istore(maxLocalsInOtherList + 1));
break;
case Opcodes.ISTORE_2:
newList.replace(i, factory.istore(maxLocalsInOtherList + 2));
break;
case Opcodes.ISTORE_3:
newList.replace(i, factory.istore(maxLocalsInOtherList + 3));
break;
case Opcodes.ISTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
// -- DLOAD family --------------------------------------
case Opcodes.DLOAD_0:
newList.replace(i, factory.dload(maxLocalsInOtherList));
break;
case Opcodes.DLOAD_1:
newList.replace(i, factory.dload(maxLocalsInOtherList + 1));
break;
case Opcodes.DLOAD_2:
newList.replace(i, factory.dload(maxLocalsInOtherList + 2));
break;
case Opcodes.DLOAD_3:
newList.replace(i, factory.dload(maxLocalsInOtherList + 3));
break;
case Opcodes.DLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
case Opcodes.DSTORE_0:
newList.replace(i, factory.dstore(maxLocalsInOtherList));
break;
case Opcodes.DSTORE_1:
newList.replace(i, factory.dstore(maxLocalsInOtherList + 1));
break;
case Opcodes.DSTORE_2:
newList.replace(i, factory.dstore(maxLocalsInOtherList + 2));
break;
case Opcodes.DSTORE_3:
newList.replace(i, factory.dstore(maxLocalsInOtherList + 3));
break;
case Opcodes.DSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
// -- LLOAD family --------------------------------------
case Opcodes.LLOAD_0:
newList.replace(i, factory.lload(maxLocalsInOtherList));
break;
case Opcodes.LLOAD_1:
newList.replace(i, factory.lload(maxLocalsInOtherList + 1));
break;
case Opcodes.LLOAD_2:
newList.replace(i, factory.lload(maxLocalsInOtherList + 2));
break;
case Opcodes.LLOAD_3:
newList.replace(i, factory.lload(maxLocalsInOtherList + 3));
break;
case Opcodes.LLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
case Opcodes.LSTORE_0:
newList.replace(i, factory.lstore(maxLocalsInOtherList));
break;
case Opcodes.LSTORE_1:
newList.replace(i, factory.lstore(maxLocalsInOtherList + 1));
break;
case Opcodes.LSTORE_2:
newList.replace(i, factory.lstore(maxLocalsInOtherList + 2));
break;
case Opcodes.LSTORE_3:
newList.replace(i, factory.lstore(maxLocalsInOtherList + 3));
break;
case Opcodes.LSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
// -- FLOAD family --------------------------------------
case Opcodes.FLOAD_0:
newList.replace(i, factory.fload(maxLocalsInOtherList));
break;
case Opcodes.FLOAD_1:
newList.replace(i, factory.fload(maxLocalsInOtherList + 1));
break;
case Opcodes.FLOAD_2:
newList.replace(i, factory.fload(maxLocalsInOtherList + 2));
break;
case Opcodes.FLOAD_3:
newList.replace(i, factory.fload(maxLocalsInOtherList + 3));
break;
case Opcodes.FLOAD:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
case Opcodes.FSTORE_0:
newList.replace(i, factory.fstore(maxLocalsInOtherList));
break;
case Opcodes.FSTORE_1:
newList.replace(i, factory.fstore(maxLocalsInOtherList + 1));
break;
case Opcodes.FSTORE_2:
newList.replace(i, factory.fstore(maxLocalsInOtherList + 2));
break;
case Opcodes.FSTORE_3:
newList.replace(i, factory.fstore(maxLocalsInOtherList + 3));
break;
case Opcodes.FSTORE:
// Handle this differently. This
// form of handling does not break exception table
// So, we minimize damage by handling this differently
ins.getBytes()[1] += (byte)maxLocalsInOtherList;
break;
}
}
return newList;
}
/**
* Process all the advice triggers found from advice() method,
* such as a call to getHotSpotName()
*/
private void processAdviceTriggers(InstructionList il, HotSpot hs) {
InstructionFactory factory = il.getInstructionFactory();
// Search for calls to possible trigger methods in
// advice method.
HotSpotLocator hsl = new HotSpotLocator(il, Opcodes.INVOKEVIRTUAL);
HotSpot[] triggerCandidates = hsl.getHotSpots();
for (int i = 0; i < triggerCandidates.length; i++) {
Invocation inv =
(Invocation)triggerCandidates[i].getHotSpotInstruction();
if (!inv.getClassName().equals(adviceMethod.getDeclaringClass().getName())) {
// Process only calls to HotSpotAdvice class
continue;
}
// Index of the trigger instruction
int idx = il.indexOf(inv);
// Following assumes, that trigger call is
// void-void method.
// First, we remove a call to Advice class...
InstructionList view = il.createView(idx-1, idx + 1);
view.clear();
// ...then we replace, that call with trigger action
// instructions.
if ("getHotSpotName".equals(inv.getMethodName())) {
view.add(factory.pushConstant(hs.getName()));
}
else if ("getArguments".equals(inv.getMethodName())) {
// NOT IMPLEMENTED
// This needs some further thinking before implementing
}
else if ("getInstrumentedObject".equals(inv.getMethodName())) {
JiapiMethod jm = il.getDeclaringMethod();
int modifiers = jm.getModifiers();
if (Modifier.isStatic(modifiers)) {
// Static methods do not have reference to 'this'
//view.add(factory.pushNull());
view.add(factory.pushConstant(jm.getDeclaringClass().getName()));
view.add(factory.invoke(Modifier.STATIC, "java.lang.Class", "forName", new Signature("java.lang.Class", new String[] {"java.lang.String"})));
}
else {
view.add(factory.aload(0));
}
// NOT IMPLEMENTED
// This needs some further thinking before implementing
}
}
}
}
public void checkForBranchTarget(Instruction firstHotSpotInstruction,
InstructionList methodList,
Instruction firstBeforeInstruction) {
for (int idx = 0; idx < methodList.size(); idx++) {
Instruction i = (Instruction)methodList.get(idx);
if (i instanceof BranchInstruction) {
BranchInstruction bi = (BranchInstruction)i;
if (bi.getTarget() == firstHotSpotInstruction) {
bi.setTarget(firstBeforeInstruction);
}
}
}
}
}