/*
* $Id: ArraysUtil.java 1011 2008-06-16 17:57:36Z amandel $
*
* Copyright 2006, The jCoderZ.org Project. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * 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.
* * Neither the name of the jCoderZ.org Project 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 REGENTS 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 REGENTS AND 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.
*/
package org.jcoderz.commons.tracing;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jcoderz.commons.util.Assert;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
/**
* Takes care to add Tracing to the methods.
* @author mandelan
*/
public class MethodTracingInjector
{
private static final String CLASSNAME
= MethodTracingInjector.class.getName();
private static final Logger logger
= Logger.getLogger(CLASSNAME);
private static final String JAVA_LOGGER = "java/util/logging/Logger";
/** Link to the class injector where this method belongs to. */
private final ClassTracingInjector mClassInjector;
/** The asm MethodNode of the method under injection. */
private final MethodNode mMethodNode;
/** Method name as it is used in the logger calls. */
private final String mMethodName;
/** The local variable that is used to sore the is loggable boolean. */
private LocalVariableNode mIsLoggableVar;
/** Method start label - after logging injection */
private LabelNode mMethodStart;
/**
* Method start label - before logging injection - after the
* isLoggable assignment.
*/
private LabelNode mOldMethodStart;
/** Label at the very end of the method. */
private LabelNode mMethodEnd;
/**
* Create a new MethodTracingInjector.
* @param mn the method node where to inject the tracing logging.
* @param ci the class injector where this method belongs to
*/
public MethodTracingInjector(MethodNode mn, ClassTracingInjector ci)
{
mClassInjector = ci;
mMethodNode = mn;
mMethodName = mn.name;
}
/**
* Do the job.
*/
public void inject ()
{
// NO abstract nor native methods
if ((mMethodNode.access
& (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) == 0)
{
int lineNumber = getLineNumber();
if (logger.isLoggable(Level.FINEST))
{
logger.finest("Injecting tracing into "
+ AsmUtil.toString(mMethodNode) + "at line " + lineNumber
+ '.');
}
mMethodStart = new LabelNode();
mOldMethodStart = getOrCreateStartLabel();
mMethodEnd = getOrCreateEndLabel();
final InsnList cmd = new InsnList();
cmd.add(mMethodStart);
if (lineNumber != -1)
{
cmd.add(new LineNumberNode(lineNumber - 1, mMethodStart));
}
if ("<clinit>".equals(mMethodNode.name))
{ // Refactor??
mClassInjector.injectStaticLoggerMember(cmd);
}
ensureLocalVariablesAreSet();
// Create boolean method var isLoggable....
injectIsLoggableVariable(cmd);
final LabelNode labelAfterEnteringLogging = isLoggable(cmd);
injectEnteringLogging(cmd);
cmd.add(labelAfterEnteringLogging);
// TAKE CARE FOR LABEL!
// Must be shifted down for tryCatchBlocks
// MUST stay for Variables -> Move Variables!
// fix var map:
final Iterator i = mMethodNode.localVariables.iterator();
while (i.hasNext())
{
final LocalVariableNode var = (LocalVariableNode) i.next();
if (var.start.equals(mOldMethodStart))
{
var.start = mMethodStart;
}
}
mMethodNode.instructions.insertBefore(mOldMethodStart, cmd);
cmd.clear();
final TryCatchBlockNode tryCatch
= injectThrowingLogging(cmd,
labelAfterEnteringLogging);
if (tryCatch != null)
{
mMethodNode.tryCatchBlocks.add(tryCatch);
mMethodNode.instructions.insertBefore(mMethodEnd, cmd);
}
cmd.clear();
injectExitingLogger();
}
else
{
if (logger.isLoggable(Level.FINEST))
{
logger.finest("No tracing in native / abstract methods! "
+ AsmUtil.toString(mMethodNode));
}
}
}
private int getLineNumber()
{
int result = -1;
final Iterator i = mMethodNode.instructions.iterator();
while (i.hasNext())
{
final AbstractInsnNode current = (AbstractInsnNode) i.next();
if (current.getType() == AbstractInsnNode.LINE)
{
final LineNumberNode lineNumber = (LineNumberNode) current;
result = lineNumber.line;
break;
}
}
return result;
}
private int getLastLineNumber()
{
int result = -1;
final Iterator i = mMethodNode.instructions.iterator();
while (i.hasNext())
{
final AbstractInsnNode current = (AbstractInsnNode) i.next();
if (current.getType() == AbstractInsnNode.LINE)
{
final LineNumberNode lineNumber = (LineNumberNode) current;
result = Math.max(lineNumber.line, result);
}
}
return result;
}
private void injectEnteringLogging(InsnList cmd)
{
// Check arguments
final Type[] arguments;
if (mMethodNode.desc != null)
{
arguments = Type.getArgumentTypes(mMethodNode.desc);
}
else
{
arguments = new Type[0];
}
mClassInjector.getStaticLoggerOnStack(cmd);
mClassInjector.getClassNameOnStack(cmd);
getMethodNameOnStack(cmd);
if (arguments == null || arguments.length == 0)
{
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"entering",
"(Ljava/lang/String;Ljava/lang/String;)V"));
}
else if (arguments.length == 1)
{
boxMe(cmd, isStatic() ? 0 : 1, arguments[0]);
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"entering",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"));
}
else
{
AsmUtil.loadConstant(cmd, arguments.length);
cmd.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"));
final int[] argPos = getArguments(arguments);
for (int i = 0; i < arguments.length; i++)
{
cmd.add(new InsnNode(Opcodes.DUP));
AsmUtil.loadConstant(cmd, i);
boxMe(cmd, argPos[i], arguments[i]);
cmd.add(new InsnNode(Opcodes.AASTORE));
}
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"entering",
"(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)V"));
}
}
private void injectExitingLogger()
{
final Iterator i = mMethodNode.instructions.iterator();
while (i.hasNext())
{
final AbstractInsnNode current = (AbstractInsnNode) i.next();
final int opcode = current.getOpcode();
LabelNode labelAfterLogging = null;
InsnList cmd = null;
switch (opcode)
{
case Opcodes.RETURN:
cmd = new InsnList();
labelAfterLogging = isLoggable(cmd);
mClassInjector.getStaticLoggerOnStack(cmd);
mClassInjector.getClassNameOnStack(cmd);
getMethodNameOnStack(cmd);
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"exiting",
"(Ljava/lang/String;Ljava/lang/String;)V"));
cmd.add(labelAfterLogging);
mMethodNode.instructions.insertBefore(current, cmd);
break;
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
cmd = new InsnList();
final Type type = Type.getReturnType(mMethodNode.desc);
labelAfterLogging = isLoggable(cmd);
final LocalVariableNode resultVar
= storeTypeOnStackInNewVariable(cmd, labelAfterLogging,
type);
mClassInjector.getStaticLoggerOnStack(cmd);
mClassInjector.getClassNameOnStack(cmd);
getMethodNameOnStack(cmd);
boxMe(cmd, resultVar);
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"exiting",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V"));
cmd.add(labelAfterLogging);
mMethodNode.instructions.insertBefore(current, cmd);
break;
default:
// stepping forward in method
break;
}
}
}
private TryCatchBlockNode injectThrowingLogging(InsnList cmd,
LabelNode labelAfterEnteringLogging)
{
final LabelNode lastStatement = beforeLastStatement();
TryCatchBlockNode result = null;
if (!near(lastStatement, labelAfterEnteringLogging))
{
final LabelNode methodEnd = new LabelNode();
cmd.add(methodEnd);
final LabelNode labelAfterThrowingLogging = isLoggable(cmd);
final LocalVariableNode exVar
= storeTypeOnStackInNewVariable(
cmd, labelAfterThrowingLogging, TracingInjector.THROWABLE_TYPE);
mClassInjector.getStaticLoggerOnStack(cmd);
mClassInjector.getClassNameOnStack(cmd);
getMethodNameOnStack(cmd);
cmd.add(new VarInsnNode(Opcodes.ALOAD, exVar.index));
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"throwing",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V"));
cmd.add(labelAfterThrowingLogging);
cmd.add(new InsnNode(Opcodes.ATHROW));
final LabelNode theEnd = new LabelNode();
cmd.add(theEnd);
// mIsLoggableVar.end = theEnd;
result = new TryCatchBlockNode(labelAfterEnteringLogging,
lastStatement, methodEnd,
TracingInjector.THROWABLE_TYPE.getInternalName());
}
return result;
}
/**
* Checks if the 2 labels are near to each other (not separated by code).
* @return true if the 2 labels are near to each other
*/
private boolean near(LabelNode labelA, LabelNode labelB)
{
boolean result = false;
AbstractInsnNode a = labelA;
while (a != null &&
( a.getType() == AbstractInsnNode.LABEL
|| a.getType() == AbstractInsnNode.LINE))
{
if (a == labelB)
{
result = true;
break;
}
a = a.getNext();
}
if (!result)
{
a = labelA;
while (a != null &&
( a.getType() == AbstractInsnNode.LABEL
|| a.getType() == AbstractInsnNode.LINE))
{
if (a == labelB)
{
result = true;
break;
}
a = a.getPrevious();
}
}
return result;
}
private LabelNode beforeLastStatement()
{
AbstractInsnNode last = mMethodNode.instructions.getLast();
while (last != null &&
(last.getType() == AbstractInsnNode.LINE
|| last.getType() == AbstractInsnNode.LABEL))
{
last = last.getPrevious();
}
// now check for a label...
AbstractInsnNode label
= (last == null ? mMethodNode.instructions.getLast() : last);
while (label != null && label.getType() == AbstractInsnNode.LINE)
{
label = label.getPrevious();
}
final LabelNode result;
if (label != null && label.getType() == AbstractInsnNode.LABEL)
{
result = (LabelNode) label;
}
else if (last == null)
{
result = new LabelNode();
mMethodNode.instructions.insert(result);
}
else
{
result = new LabelNode();
mMethodNode.instructions.insertBefore(last, result);
}
return result;
}
/**
*
*/
private void ensureLocalVariablesAreSet()
{
if (mMethodNode.localVariables == null)
{
mMethodNode.localVariables = new ArrayList();
}
}
private void injectIsLoggableVariable (InsnList cmd)
{
final int varIndex = getFreeLocalIndex();
final LabelNode varStartNode = new LabelNode();
mIsLoggableVar
= new LocalVariableNode(
TracingInjector.IS_LOGGABLE_FIELD_NAME + varIndex,
Type.BOOLEAN_TYPE.getDescriptor(),
(String) null, varStartNode, mMethodEnd, varIndex);
mMethodNode.localVariables.add(mIsLoggableVar);
mClassInjector.getStaticLoggerOnStack(cmd);
cmd.add(new FieldInsnNode(
Opcodes.GETSTATIC,
"java/util/logging/Level",
"FINER", "Ljava/util/logging/Level;"));
cmd.add(new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
JAVA_LOGGER,
"isLoggable",
"(Ljava/util/logging/Level;)Z"));
cmd.add(new VarInsnNode(Opcodes.ISTORE, mIsLoggableVar.index));
cmd.add(varStartNode);
}
/**
* Returns an array holding the Variable index of the corresponding Argument.
* @param types an array holding the argument Types.
* @return
*/
private int[] getArguments(Type[] types)
{
final int[] result = new int[types.length];
int cur = isStatic() ? 0 : 1;
for (int pos = 0; pos < types.length; pos++)
{
result[pos] = cur;
cur += types[pos].getSize();
}
return result;
}
/**
*
* @param cmd
*/
private void getMethodNameOnStack(InsnList cmd)
{
cmd.add(new LdcInsnNode(mMethodName));
}
/**
*
* @return
*/
private int getFreeLocalIndex ()
{
final Iterator i = mMethodNode.localVariables.iterator();
int maxIndex = -1;
while (i.hasNext())
{
final LocalVariableNode node = (LocalVariableNode) i.next();
if (node.index >= maxIndex)
// >= needed because index could be reused with larger type
{
if (node.desc.equals(Type.DOUBLE_TYPE.getDescriptor())
|| node.desc.equals(Type.LONG_TYPE.getDescriptor()))
{
maxIndex = node.index + 1;
}
else
{
maxIndex = node.index;
}
}
}
maxIndex++;
final int other = getFreeLocalByCode();
return Math.max(other, maxIndex);
}
/**
*
* @return
*/
private int getFreeLocalByCode ()
{
int maxIndex = -1;
final Iterator i = mMethodNode.instructions.iterator();
while (i.hasNext())
{
final AbstractInsnNode insn = (AbstractInsnNode) i.next();
if (insn instanceof VarInsnNode)
{
final VarInsnNode varInsnNode = (VarInsnNode) insn;
if (varInsnNode.var >= maxIndex)
{ // >= needed because index could be reused with larger type
switch (varInsnNode.getOpcode())
{
case Opcodes.ILOAD:
case Opcodes.FLOAD:
case Opcodes.ALOAD:
case Opcodes.ISTORE:
case Opcodes.FSTORE:
case Opcodes.ASTORE:
maxIndex = varInsnNode.var;
break;
case Opcodes.LLOAD:
case Opcodes.DLOAD:
case Opcodes.LSTORE:
case Opcodes.DSTORE:
maxIndex = varInsnNode.var + 1;
break;
case Opcodes.RET:
// ignore already covered with the ASTORE
break;
default:
Assert.assertTrue(
"Unexpected opcode " + insn.getOpcode()
+ " in VarInsnNode operation.", false);
break;
}
}
}
}
return maxIndex + 1;
}
/**
* Expects data of type type on the Stacks and stores it in a new Variable,
* the stack content stays unchanged!
* @param cmd the instruction list to add the commands.
* @param end intended end label for the validity of the variable
* @param type the type of the data to store
* @return the new generated variable
*/
private LocalVariableNode storeTypeOnStackInNewVariable(InsnList cmd,
LabelNode end, final Type type)
{
// TODO: Could be more efficient in reusing free indexes
// But we can not relay on the localVariables table...
// CREATE VAR
// The label is the position AFTER the xSTORE cmd.
final LabelNode startLabel = new LabelNode();
final int index = getFreeLocalIndex();
final LocalVariableNode resultVar
= new LocalVariableNode(
TracingInjector.RESULT_VAR_FIELD_NAME + index,
type.getDescriptor(), (String) null, startLabel, end,
index);
mMethodNode.localVariables.add(resultVar);
// CREATE CODE
cmd.add(new InsnNode(type.getSize() == 1 ? Opcodes.DUP : Opcodes.DUP2));
cmd.add(new VarInsnNode(
type.getOpcode(Opcodes.ISTORE), resultVar.index));
cmd.add(startLabel);
// Load the var again (could use the DUP above)
// cmd.add(new VarInsnNode(
// type.getOpcode(Opcodes.ILOAD), resultVar.index));
return resultVar;
}
/**
*
* @return
*/
private boolean isStatic ()
{
return (mMethodNode.access & Opcodes.ACC_STATIC) != 0;
}
/**
*
* @param cmd
* @param var
*/
private void boxMe(InsnList cmd, LocalVariableNode var)
{
Assert.notNull(cmd, "cmd");
Assert.notNull(var, "var");
boxMe(cmd, var.index, Type.getType(var.desc));
}
/**
*
* @param cmd
* @param index
* @param type
*/
private void boxMe(InsnList cmd, int index, Type type)
{
switch (type.getSort())
{
case Type.SHORT:
boxMe(cmd, type, "java/lang/Short", index);
break;
case Type.INT:
boxMe(cmd, type, "java/lang/Integer", index);
break;
case Type.CHAR:
boxMe(cmd, type, "java/lang/Character", index);
break;
case Type.BOOLEAN:
boxMe(cmd, type, "java/lang/Boolean", index);
break;
case Type.LONG:
boxMe(cmd, type, "java/lang/Long", index);
break;
case Type.FLOAT:
boxMe(cmd, type, "java/lang/Float", index);
break;
case Type.DOUBLE:
boxMe(cmd, type, "java/lang/Double", index);
break;
case Type.BYTE:
boxMe(cmd, type, "java/lang/Byte", index);
break;
case Type.ARRAY:
case Type.OBJECT:
cmd.add(new VarInsnNode(
type.getOpcode(Opcodes.ILOAD), index));
break;
default:
throw new RuntimeException("Unexpected type " + type.getDescriptor());
}
}
/**
*
* @param instr
* @param type
* @param boxClassName
* @param index
*/
private void boxMe(InsnList instr, Type type, String boxClassName,
int index)
{
if (!(type.getSort() == Type.BOOLEAN) && !mClassInjector.isJava5())
{
// new Xxxxx(...)
instr.add(new TypeInsnNode(Opcodes.NEW, boxClassName));
instr.add(new InsnNode(Opcodes.DUP));
instr.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), index));
instr.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, boxClassName, "<init>",
"(" + type.getDescriptor() + ")V"));
}
else
{ // since JDK 1.5 version (except for boolean):
// Xxxxx.valueOf(...)
instr.add(new VarInsnNode(
type.getOpcode(Opcodes.ILOAD), index));
instr.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, boxClassName, "valueOf",
"(" + type.getDescriptor() + ")L" + boxClassName + ";"));
}
}
/**
*
* @return
*/
private LabelNode getOrCreateEndLabel ()
{
final AbstractInsnNode last = mMethodNode.instructions.getLast();
final LabelNode result;
if (last.getType() == AbstractInsnNode.LABEL)
{
result = (LabelNode) last;
}
else
{
// need a new end Label...
result = new LabelNode();
mMethodNode.instructions.add(result);
}
return result;
}
/**
*
* @return
*/
private LabelNode getOrCreateStartLabel ()
{
final AbstractInsnNode first = mMethodNode.instructions.getFirst();
final LabelNode result;
if (first.getType() == AbstractInsnNode.LABEL)
{
result = (LabelNode) first;
}
else
{ // need a start Label...
result = new LabelNode();
mMethodNode.instructions.insertBefore(first, result);
}
return result;
}
/**
* Create ifLoggable code and return a label to jump to
* in case of a false result.
* @param cmd the instruction list to add the instructions to.
* @return the node that is used as target if the isLoggable
* evaluates to false.
*/
private LabelNode isLoggable (InsnList cmd)
{
cmd.add(new VarInsnNode(Opcodes.ILOAD, mIsLoggableVar.index));
final LabelNode afterLogging = new LabelNode();
cmd.add(new JumpInsnNode(Opcodes.IFEQ, afterLogging));
return afterLogging;
}
}