mirror of https://github.com/mchr3k/org.intrace
609 lines
19 KiB
Java
609 lines
19 KiB
Java
package org.intrace.agent;
|
|
|
|
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import org.intrace.output.AgentHelper;
|
|
import org.objectweb.asm.ClassReader;
|
|
import org.objectweb.asm.ClassWriter;
|
|
import org.objectweb.asm.Label;
|
|
import org.objectweb.asm.MethodAdapter;
|
|
import org.objectweb.asm.MethodVisitor;
|
|
import org.objectweb.asm.Opcodes;
|
|
import org.objectweb.asm.Type;
|
|
|
|
/**
|
|
* ASM2 ClassWriter used to transform class files to instrument methods to add
|
|
* calls into {@link AgentHelper}.
|
|
*/
|
|
public class InstrumentedClassWriter extends ClassWriter
|
|
{
|
|
private final String className;
|
|
private final ClassAnalysis analysis;
|
|
private int methodNum = 1;
|
|
|
|
/**
|
|
* cTor
|
|
*
|
|
* @param xiClassName
|
|
* @param xiReader
|
|
* @param analysis
|
|
*/
|
|
public InstrumentedClassWriter(String xiClassName, ClassReader xiReader,
|
|
ClassAnalysis xiAnalysis)
|
|
{
|
|
super(xiReader, COMPUTE_MAXS);
|
|
className = xiClassName;
|
|
analysis = xiAnalysis;
|
|
}
|
|
|
|
/**
|
|
* Instrument a particular method.
|
|
*/
|
|
@Override
|
|
public MethodVisitor visitMethod(int access, String name, String desc,
|
|
String signature, String[] exceptions)
|
|
{
|
|
MethodVisitor mv = super.visitMethod(access, name, desc, signature,
|
|
exceptions);
|
|
|
|
// Extract analysis results for this method
|
|
Set<Integer> branchTraceLines = analysis.methodReverseGOTOLines.get(name
|
|
+ desc);
|
|
Integer entryLine = analysis.methodEntryLines.get(name + desc);
|
|
|
|
// Transform the method
|
|
return new InstrumentedMethodWriter(mv, access, name, desc,
|
|
branchTraceLines, entryLine);
|
|
}
|
|
|
|
/**
|
|
* ASM2 MethodVisitor used to instrument methods.
|
|
*/
|
|
private class InstrumentedMethodWriter extends MethodAdapter
|
|
{
|
|
private static final String HELPER_CLASS = "org/intrace/output/AgentHelper";
|
|
|
|
// Final method fields
|
|
private final String methodName;
|
|
private final String methodDescriptor;
|
|
private final int methodAccess;
|
|
|
|
// Analysis data
|
|
private final Set<Integer> reverseGOTOLines;
|
|
private final Integer entryLine;
|
|
private final Map<Label, Integer> labelLineNos = new HashMap<Label, Integer>();
|
|
private final Set<Label> traceLabels = new HashSet<Label>();
|
|
private final Set<Label> exceptionHandlerLabels = new HashSet<Label>();
|
|
|
|
// State
|
|
private boolean writeTraceLine = false;
|
|
private CTorEntryState ctorEntryState = CTorEntryState.NORMALMETHOD;
|
|
private int lineNumber = -1;
|
|
private int numBranchesOnLine = 0;
|
|
private TernaryState ternState = TernaryState.BASE;
|
|
|
|
/**
|
|
* cTor
|
|
*
|
|
* @param xiMethodVisitor
|
|
* @param access
|
|
* @param xiClassName
|
|
* @param xiMethodName
|
|
* @param xiDesc
|
|
* @param xiBranchTraceLines
|
|
* @param entryLine
|
|
*/
|
|
public InstrumentedMethodWriter(MethodVisitor xiMethodVisitor, int access,
|
|
String xiMethodName, String xiDesc, Set<Integer> xiBranchTraceLines,
|
|
Integer xiEntryLine)
|
|
{
|
|
super(xiMethodVisitor);
|
|
methodAccess = access;
|
|
methodName = xiMethodName;
|
|
methodDescriptor = xiDesc;
|
|
reverseGOTOLines = xiBranchTraceLines;
|
|
entryLine = xiEntryLine;
|
|
if (methodName.equals("<init>"))
|
|
{
|
|
ctorEntryState = CTorEntryState.ISCTOR;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initial entry point - generate ENTRY call.
|
|
*/
|
|
@Override
|
|
public void visitCode()
|
|
{
|
|
methodNum++;
|
|
if (ctorEntryState != CTorEntryState.ISCTOR)
|
|
{
|
|
addEntryCalls();
|
|
}
|
|
// For Constructors we add the entry calls after the first invokeSpecial
|
|
// which calls into the superclass constructor.
|
|
super.visitCode();
|
|
}
|
|
|
|
private void addEntryCalls()
|
|
{
|
|
generateCallToAgentHelper(InstrumentationType.ENTER,
|
|
((entryLine != null ? entryLine
|
|
: -1)));
|
|
traceMethodArgs();
|
|
}
|
|
|
|
/**
|
|
* Pass the args of this method out to the {@link AgentHelper}
|
|
*/
|
|
private void traceMethodArgs()
|
|
{
|
|
Type[] argTypes = Type.getArgumentTypes(methodDescriptor);
|
|
boolean isStaticAccess = ((methodAccess & Opcodes.ACC_STATIC) > 0);
|
|
int offset = (isStaticAccess ? 0
|
|
: 1);
|
|
List<String> argNames = analysis.methodArgNames.get(methodName + methodDescriptor);
|
|
|
|
for (int ii = 0; ii < argTypes.length; ii++)
|
|
{
|
|
String typeDescriptor = argTypes[ii].getDescriptor();
|
|
|
|
int varslot = ii + offset;
|
|
int opcode = Opcodes.ILOAD;
|
|
if (argTypes[ii].getSort() == Type.OBJECT)
|
|
{
|
|
typeDescriptor = "Ljava/lang/Object;";
|
|
opcode = Opcodes.ALOAD;
|
|
}
|
|
else if ((argTypes[ii].getSort() == Type.ARRAY)
|
|
&& (argTypes[ii].getDescriptor().startsWith("[[")))
|
|
{
|
|
// All multidimensional arrays are handled by the object array
|
|
// function
|
|
typeDescriptor = "[Ljava/lang/Object;";
|
|
}
|
|
else if ((argTypes[ii].getSort() == Type.ARRAY)
|
|
&& (argTypes[ii].getDescriptor().startsWith("[L")))
|
|
{
|
|
// All object arrays are cast to the object supertype
|
|
typeDescriptor = "[Ljava/lang/Object;";
|
|
}
|
|
else if (argTypes[ii].getSort() == Type.LONG)
|
|
{
|
|
opcode = Opcodes.LLOAD;
|
|
offset++;
|
|
}
|
|
else if (argTypes[ii].getSort() == Type.FLOAT)
|
|
{
|
|
opcode = Opcodes.FLOAD;
|
|
}
|
|
else if (argTypes[ii].getSort() == Type.DOUBLE)
|
|
{
|
|
opcode = Opcodes.DLOAD;
|
|
offset++;
|
|
}
|
|
|
|
if (argTypes[ii].getSort() == Type.ARRAY)
|
|
{
|
|
opcode = Opcodes.ALOAD;
|
|
}
|
|
|
|
if ((argNames != null) && (argNames.size() > ii))
|
|
{
|
|
mv.visitLdcInsn("Arg (" + argNames.get(ii) + ")");
|
|
}
|
|
else
|
|
{
|
|
mv.visitLdcInsn("Arg (unknown)");
|
|
}
|
|
mv.visitLdcInsn(className);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitVarInsn(opcode, varslot);
|
|
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;"
|
|
+ typeDescriptor + ")V");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Guard against a label which hasn't been resolved. This happens sometimes,
|
|
* I assume due to a bug in ASM.
|
|
*/
|
|
@Override
|
|
public void visitLocalVariable(String name, String desc, String signature,
|
|
Label start, Label end, int index)
|
|
{
|
|
try
|
|
{
|
|
end.getOffset();
|
|
}
|
|
catch (IllegalStateException ex)
|
|
{
|
|
mv.visitLabel(end);
|
|
}
|
|
super.visitLocalVariable(name, desc, signature, start, end, index);
|
|
}
|
|
|
|
/**
|
|
* Write branch trace.
|
|
*/
|
|
@Override
|
|
public void visitLineNumber(int xiLineNumber, Label label)
|
|
{
|
|
lineNumber = xiLineNumber;
|
|
labelLineNos.put(label, xiLineNumber);
|
|
|
|
if (writeTraceLine || reverseGOTOLines.contains(xiLineNumber)
|
|
|| traceLabels.contains(label))
|
|
{
|
|
// This check excludes ternary statements where we can't add trace
|
|
if (numBranchesOnLine < 2)
|
|
{
|
|
if (ctorEntryState == CTorEntryState.SEEN_SPECIAL)
|
|
{
|
|
addEntryCalls();
|
|
ctorEntryState = CTorEntryState.ENTRY_WRITTEN;
|
|
}
|
|
else
|
|
{
|
|
if (exceptionHandlerLabels.contains(label))
|
|
{
|
|
// Top of the stack contains an exception - generate code to trace
|
|
// it
|
|
// Duplicate the exception
|
|
mv.visitInsn(Opcodes.DUP);
|
|
|
|
// Load args
|
|
mv.visitLdcInsn("Caught");
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(className);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(lineNumber);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
|
|
// Generate call to trace exception
|
|
mv
|
|
.visitMethodInsn(
|
|
INVOKESTATIC,
|
|
HELPER_CLASS,
|
|
"val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Throwable;)V");
|
|
}
|
|
else
|
|
{
|
|
generateCallToAgentHelper(InstrumentationType.BRANCH, lineNumber);
|
|
}
|
|
}
|
|
writeTraceLine = false;
|
|
}
|
|
else
|
|
{
|
|
writeTraceLine = false;
|
|
}
|
|
}
|
|
|
|
numBranchesOnLine = 0;
|
|
super.visitLineNumber(xiLineNumber, label);
|
|
}
|
|
|
|
/**
|
|
* Handle return instructions by writing exit trace.
|
|
*/
|
|
@Override
|
|
public void visitInsn(int xiOpCode)
|
|
{
|
|
if ((ternState == TernaryState.BASE) && (xiOpCode == Opcodes.DUP))
|
|
{
|
|
ternState = TernaryState.SEEN_DUP;
|
|
}
|
|
else if ((ternState == TernaryState.SEEN_BRANCH)
|
|
&& (xiOpCode == Opcodes.POP))
|
|
{
|
|
ternState = TernaryState.BASE;
|
|
writeTraceLine = false;
|
|
}
|
|
else
|
|
{
|
|
ternState = TernaryState.BASE;
|
|
}
|
|
|
|
if (xiOpCode == Opcodes.RETURN)
|
|
{
|
|
// Ensure that cTor entry call gets written even if the cTor is
|
|
// implicit and therefore has only a single line number.
|
|
if (ctorEntryState == CTorEntryState.SEEN_SPECIAL)
|
|
{
|
|
addEntryCalls();
|
|
ctorEntryState = CTorEntryState.ENTRY_WRITTEN;
|
|
}
|
|
generateCallToAgentHelper(InstrumentationType.EXIT, lineNumber);
|
|
}
|
|
else if ((xiOpCode == Opcodes.IRETURN) || (xiOpCode == Opcodes.FRETURN)
|
|
|| (xiOpCode == Opcodes.ARETURN))
|
|
{
|
|
// Duplicate the return value
|
|
mv.visitInsn(Opcodes.DUP);
|
|
|
|
// Push the callname and methodname while keeping the return value#
|
|
// at the top of the stack
|
|
mv.visitLdcInsn("Return");
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(className);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
|
|
if (xiOpCode == Opcodes.IRETURN)
|
|
{
|
|
mv
|
|
.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)V");
|
|
}
|
|
else if (xiOpCode == Opcodes.FRETURN)
|
|
{
|
|
mv
|
|
.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;F)V");
|
|
}
|
|
else if (xiOpCode == Opcodes.ARETURN)
|
|
{
|
|
mv
|
|
.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V");
|
|
}
|
|
generateCallToAgentHelper(InstrumentationType.EXIT, lineNumber);
|
|
}
|
|
else if ((xiOpCode == Opcodes.LRETURN) || (xiOpCode == Opcodes.DRETURN))
|
|
{
|
|
// Duplicate the return value
|
|
mv.visitInsn(Opcodes.DUP2);
|
|
|
|
// Push the callname and methodname while keeping the return value
|
|
// at the top of the stack
|
|
mv.visitLdcInsn("Return");
|
|
mv.visitInsn(Opcodes.DUP_X2);
|
|
mv.visitInsn(Opcodes.POP);
|
|
mv.visitLdcInsn(className);
|
|
mv.visitInsn(Opcodes.DUP_X2);
|
|
mv.visitInsn(Opcodes.POP);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitInsn(Opcodes.DUP_X2);
|
|
mv.visitInsn(Opcodes.POP);
|
|
|
|
if (xiOpCode == Opcodes.LRETURN)
|
|
{
|
|
mv
|
|
.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V");
|
|
}
|
|
else if (xiOpCode == Opcodes.DRETURN)
|
|
{
|
|
mv
|
|
.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;D)V");
|
|
}
|
|
generateCallToAgentHelper(InstrumentationType.EXIT, lineNumber);
|
|
}
|
|
else if (xiOpCode == Opcodes.ATHROW)
|
|
{
|
|
// Top of the stack contains an exception - generate code to trace
|
|
// it - Duplicate the exception
|
|
mv.visitInsn(Opcodes.DUP);
|
|
|
|
// Load args
|
|
mv.visitLdcInsn("Throw");
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(className);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
mv.visitLdcInsn(lineNumber);
|
|
mv.visitInsn(Opcodes.SWAP);
|
|
|
|
// Generate call to trace exception
|
|
mv
|
|
.visitMethodInsn(
|
|
INVOKESTATIC,
|
|
HELPER_CLASS,
|
|
"val",
|
|
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Throwable;)V");
|
|
|
|
// Also write exit trace
|
|
generateCallToAgentHelper(InstrumentationType.EXIT, lineNumber);
|
|
}
|
|
super.visitInsn(xiOpCode);
|
|
}
|
|
|
|
/**
|
|
* Handle cTor case where we write entry trace later.
|
|
*/
|
|
@Override
|
|
public void visitMethodInsn(int xiOpCode, String owner, String name,
|
|
String desc)
|
|
{
|
|
if ((ctorEntryState == CTorEntryState.ISCTOR)
|
|
&& (xiOpCode == Opcodes.INVOKESPECIAL))
|
|
{
|
|
ctorEntryState = CTorEntryState.SEEN_SPECIAL;
|
|
writeTraceLine = true;
|
|
}
|
|
|
|
super.visitMethodInsn(xiOpCode, owner, name, desc);
|
|
}
|
|
|
|
/**
|
|
* For all forward branch instructions we trace the next line we see as we
|
|
* know it is optional.
|
|
* <p>
|
|
* We don't mark the target label as we don't know whether it is optional
|
|
* code.
|
|
*/
|
|
@Override
|
|
public void visitJumpInsn(int xiOpCode, Label xiBranchLabel)
|
|
{
|
|
if (ternState == TernaryState.SEEN_DUP)
|
|
{
|
|
ternState = TernaryState.SEEN_BRANCH;
|
|
}
|
|
else
|
|
{
|
|
ternState = TernaryState.BASE;
|
|
}
|
|
|
|
numBranchesOnLine++;
|
|
|
|
Integer lineNo = labelLineNos.get(xiBranchLabel);
|
|
if (lineNo == null)
|
|
{
|
|
// This is a forward jump
|
|
writeTraceLine = true;
|
|
}
|
|
super.visitJumpInsn(xiOpCode, xiBranchLabel);
|
|
}
|
|
|
|
/**
|
|
* Try catch block - mark all labels for tracing
|
|
*/
|
|
@Override
|
|
public void visitTryCatchBlock(Label start, Label end, Label handler,
|
|
String type)
|
|
{
|
|
// Null type means that handler is a finally block which will always be
|
|
// executed
|
|
if (type != null)
|
|
{
|
|
traceLabels.add(handler);
|
|
exceptionHandlerLabels.add(handler);
|
|
}
|
|
super.visitTryCatchBlock(start, end, handler, type);
|
|
}
|
|
|
|
/**
|
|
* Table switch block - mark all labels for tracing
|
|
*/
|
|
@Override
|
|
public void visitTableSwitchInsn(int min, int max, Label dflt,
|
|
Label[] labels)
|
|
{
|
|
traceLabels.add(dflt);
|
|
for (Label label : labels)
|
|
{
|
|
traceLabels.add(label);
|
|
}
|
|
super.visitTableSwitchInsn(min, max, dflt, labels);
|
|
}
|
|
|
|
/**
|
|
* Lookup switch block - mark all labels for tracing
|
|
*/
|
|
@Override
|
|
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)
|
|
{
|
|
traceLabels.add(dflt);
|
|
for (Label label : labels)
|
|
{
|
|
traceLabels.add(label);
|
|
}
|
|
super.visitLookupSwitchInsn(dflt, keys, labels);
|
|
}
|
|
|
|
/**
|
|
* Generate an ENTER/BRANCH/EXIT instrumentation call.
|
|
*
|
|
* @param traceType
|
|
* @param lineNumber
|
|
*/
|
|
private void generateCallToAgentHelper(InstrumentationType traceType,
|
|
int lineNumber)
|
|
{
|
|
mv.visitLdcInsn(className);
|
|
mv.visitLdcInsn(methodName);
|
|
mv.visitLdcInsn(lineNumber);
|
|
switch (traceType)
|
|
{
|
|
case ENTER:
|
|
{
|
|
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "enter",
|
|
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
|
break;
|
|
}
|
|
|
|
case BRANCH:
|
|
{
|
|
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "branch",
|
|
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
|
break;
|
|
}
|
|
|
|
case EXIT:
|
|
{
|
|
mv.visitMethodInsn(INVOKESTATIC, HELPER_CLASS, "exit",
|
|
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Three way enum to signal the difference between ENTER/BRANCH/EXIT trace.
|
|
*/
|
|
private enum InstrumentationType
|
|
{
|
|
ENTER, BRANCH, EXIT;
|
|
}
|
|
|
|
/**
|
|
* Type used to suppress trace in the case where the following instruction
|
|
* sequence is seen.
|
|
* <ul>
|
|
* <li>...
|
|
* <li>DUP
|
|
* <li>BRANCH
|
|
* <li>POP
|
|
* <li>...
|
|
* </ul>
|
|
* This sequence is used in ternary if statements of the form (cond(x) ? x :
|
|
* Y). This agent currently does not support adding trace into these
|
|
* constructs.
|
|
* <p>
|
|
* MCHR: This should either be fixed or strengthened to ensure it covers all
|
|
* ternary statements. Does numBranchesOnLine already strengthen this?
|
|
*/
|
|
private enum TernaryState
|
|
{
|
|
BASE, SEEN_DUP, SEEN_BRANCH
|
|
/* SEEN_POP */
|
|
}
|
|
|
|
/**
|
|
* cTor related state.
|
|
* <p>
|
|
* Normal methods are assigned NORMALMETHOD.
|
|
* <p>
|
|
* cTors are assigned ISCTOR and then SEEN_SPECIAL once the superclass
|
|
* constructor call is issued. This allows us to avoid writing entry trace
|
|
* before the superclass constructor call.
|
|
* <p>
|
|
* ENTRY_WRITTEN is set if a line number is processed such that we write entry
|
|
* trace. Otherwise we know to write entry trace before exit trace when we
|
|
* visit a return instruction. This is mostly only useful for processing
|
|
* implicit constructors.
|
|
* <p>
|
|
* MCHR: Ensure this all works in the case of a constructor calling through to
|
|
* another constructor in the same class.
|
|
*/
|
|
private enum CTorEntryState
|
|
{
|
|
NORMALMETHOD, ISCTOR, SEEN_SPECIAL, ENTRY_WRITTEN
|
|
}
|
|
}
|