/* * Copyright 2012 Kantega AS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kantega.revoc.instrumentation; import org.kantega.revoc.analysis.OneLineAnalyze; import org.kantega.revoc.registry.BranchPoint; import org.kantega.revoc.registry.Registry; import org.kantega.revoc.registry.ThreadLocalBuffer; import org.objectweb.asm.*; import org.objectweb.asm.commons.AdviceAdapter; import org.objectweb.asm.tree.*; import java.util.*; /** * */ public class CoverageClassVisitor extends ClassVisitor implements Opcodes { /** Representing this class with an integer which can be used as an array index **/ private final int classId; /** Lines in this class file will have bits set for each line containing line number instructions **/ private final BitSet existingLines = new BitSet(); /** Maps actual debug line number to the index of its first usage **/ private final Map<Integer, Integer> classLineNumbers = new HashMap<Integer, Integer>(); private final Map<Integer, List<Integer>> methodLineNumbers = new TreeMap<Integer, List<Integer>>(); private List<String> innerClasses = new ArrayList<String>(); private List<BranchPoint> branchPoints = new ArrayList<BranchPoint>(); private String source; private String className; private boolean trackLines = true; private boolean trackTime = true; private boolean trackBranches = false; private boolean profile = false; private boolean profileTime = false; private int access; private int maxLocalVariableReportLoad = 10000; private List<String> methodNames = new ArrayList<String>(); private List<String> methodDescs = new ArrayList<String>(); private boolean staticInjected = false; private static Set<String> supressedMethodNames = new HashSet<String>(); static { //supressedMethodNames.add("loop"); //supressedMethodNames.add("innerLoop"); //supressedMethodNames.add("fastMethod"); //supressedMethodNames.add("fasterMethod"); } public CoverageClassVisitor(ClassVisitor classVisitor, int classId) { super(ASM5, classVisitor); this.classId = classId; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); className = name; this.access = access; } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { super.visitInnerClass(name, outerName, innerName, access); if(!className.equals(name)) { innerClasses.add(name); } } @Override public void visitSource(String source, String debug) { super.visitSource(source, debug); this.source = source; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { final MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (!name.startsWith("lambda$") && (access & ACC_SYNTHETIC) != 0) { return mv; } return new FirstPassAnalysis(mv, access, name, desc, signature, exceptions); } @Override public void visitEnd() { FieldVisitor lineCounter = super.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC + ACC_FINAL, "revoc_counters", "Ljava/util/concurrent/atomic/AtomicLongArray;", null, null); lineCounter.visitEnd(); FieldVisitor timeCounter = super.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC + ACC_FINAL, "revoc_times", "Ljava/util/concurrent/atomic/AtomicLongArray;", null, null); timeCounter.visitEnd(); FieldVisitor methodCounter = super.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC + ACC_FINAL, "revoc_method_counters", "Lorg/kantega/revoc/registry/UnsafeAtomicLongArray;", null, null); methodCounter.visitEnd(); if(!staticInjected) { MethodVisitor mv = super.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null); mv.visitCode(); visitFetchRevocCounter(mv); mv.visitInsn(RETURN); mv.visitMaxs(2, 0); mv.visitEnd(); } super.visitEnd(); } private void visitFetchRevocCounter(MethodVisitor mv) { { mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "lineVisits", "[Ljava/util/concurrent/atomic/AtomicLongArray;"); mv.visitLdcInsn(classId); mv.visitInsn(AALOAD); mv.visitFieldInsn(PUTSTATIC, className, "revoc_counters", "Ljava/util/concurrent/atomic/AtomicLongArray;"); } { mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "methodVisits", "[Lorg/kantega/revoc/registry/UnsafeAtomicLongArray;"); mv.visitLdcInsn(classId); mv.visitInsn(AALOAD); mv.visitFieldInsn(PUTSTATIC, className, "revoc_method_counters", "Lorg/kantega/revoc/registry/UnsafeAtomicLongArray;"); } { mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "lineTimes", "[Ljava/util/concurrent/atomic/AtomicLongArray;"); mv.visitLdcInsn(classId); mv.visitInsn(AALOAD); mv.visitFieldInsn(PUTSTATIC, className, "revoc_times", "Ljava/util/concurrent/atomic/AtomicLongArray;"); } } protected MethodVisitor createSecondPassAnalyzer(int classId, Map<Integer, Integer> classLineNumbers, Map<Integer, Integer> methodLineNumbers, Map<Integer, Integer> branchPoints, int reportLoad, OneLineAnalyze oneTimeLines, MethodVisitor mv, int access, String name, String desc) { return new SecondPassInstrumentation(classId, classLineNumbers, methodLineNumbers, branchPoints, reportLoad, oneTimeLines, mv, access, name, desc); } public int getClassId() { return classId; } public void setTrackTime(boolean trackTime) { this.trackTime = trackTime; } public void setTrackBranches(boolean trackBranches) { this.trackBranches = trackBranches; } public Map<Integer, List<Integer>> getMethodLineNumbers() { return methodLineNumbers; } /** * Runs a first pass of the code such that instrumentation can be done on the basis of class analysis. */ class FirstPassAnalysis extends MethodNode { private final MethodVisitor mv; public FirstPassAnalysis(MethodVisitor mv, int access, String name, String desc, String signature, String[] exceptions) { super(Opcodes.ASM5, access, name, desc, signature, exceptions); this.mv = mv; } @Override public void visitEnd() { if(supressedMethodNames.contains(name) || (access & Opcodes.ACC_ABSTRACT) != 0 ) { accept(new MethodVisitor(ASM5, mv) { }); } else { final Map<Integer, Integer> methodLineNumbers = analyzeLinePoints(instructions); final Map<Integer, Integer> branchPoints = analyzeBranchPoints(instructions); final OneLineAnalyze oneTimeLines = OneLineAnalyze.analyze(this); int numExitPoints = countExitPoints(instructions); int reportLoad = (methodLineNumbers.size() + branchPoints.size()) * numExitPoints; accept(createSecondPassAnalyzer(classId, classLineNumbers, methodLineNumbers, branchPoints, reportLoad, oneTimeLines, mv, access, name, desc)); methodNames.add(name); methodDescs.add(desc); } } private int countExitPoints(InsnList instructions) { int numberOfReturns = 0; for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode ins = instructions.get(i); if (ins instanceof InsnNode) { InsnNode node = (InsnNode) ins; if((node.getOpcode() >= IRETURN && node.getOpcode() <= RETURN ) || node.getOpcode() == ATHROW) { numberOfReturns ++; } } } return numberOfReturns; } private Map<Integer, Integer> analyzeBranchPoints(InsnList instructions) { int currentLineNumber = 0; final Map<Integer, Integer> branchPoints = new TreeMap<Integer, Integer>(); for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode ins = instructions.get(i); if (ins instanceof LineNumberNode) { LineNumberNode node = (LineNumberNode) ins; currentLineNumber = node.line; } if (ins instanceof JumpInsnNode) { JumpInsnNode node = (JumpInsnNode) ins; if (node.getOpcode() != Opcodes.GOTO && node.getOpcode() != Opcodes.JSR) { int globalIndex = CoverageClassVisitor.this.branchPoints.size(); CoverageClassVisitor.this.branchPoints.add(new BranchPoint(node.getOpcode(), currentLineNumber - 1)); branchPoints.put(branchPoints.size(), globalIndex); } } } return branchPoints; } private Map<Integer, Integer> analyzeLinePoints(InsnList instructions) { CoverageClassVisitor.this.methodLineNumbers.put(methodNames.size(), new ArrayList<Integer>()); final Map<Integer, Integer> methodLineNumbers = new TreeMap<Integer, Integer>(); for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode ins = instructions.get(i); if (ins instanceof LineNumberNode) { LineNumberNode node = (LineNumberNode) ins; int lineNumber = node.line; existingLines.set(lineNumber); int idx = classLineNumbers.size(); if (!classLineNumbers.containsKey(lineNumber)) { classLineNumbers.put(lineNumber, idx); } else { idx = classLineNumbers.get(lineNumber); } if (!methodLineNumbers.containsKey(lineNumber)) { methodLineNumbers.put(lineNumber, methodLineNumbers.size()); } List<Integer> lines = CoverageClassVisitor.this.methodLineNumbers.get(methodNames.size()); if(!lines.contains(idx)) { lines.add(idx); } } } return methodLineNumbers; } } class SecondPassInstrumentation extends AdviceAdapter { private final int classId; private final Map<Integer, Integer> classLineNumbers; private final Map<Integer, Integer> methodLineNumbers; private final Map<Integer, Integer> branchPoints; private final OneLineAnalyze oneTimeLines; private final int access; private final String name; private int methodJumpIndex = 0; private int timeLocal; private final boolean useLocalVariables; private int lineVisitsLocalVariable; private int timeVisitsLocalVariable; private int beforeBranchPointsLocalVariable; private int afterBranchPointsLocalVariable; private int frameMapLocalVariable; //private int waitTimeLocalVariable; //private int totalWaitTimeLocalVariable; private int startTimeLocalVariable; private boolean profile; private int threadBufferLocal; private boolean constructor; private int multiMethodCursorLocalVariable; protected SecondPassInstrumentation(int classId, Map<Integer, Integer> classLineNumbers, Map<Integer, Integer> methodLineNumbers, Map<Integer, Integer> branchPoints, int reportLoad, OneLineAnalyze oneTimeLines, MethodVisitor methodVisitor, int access, String name, String desc) { super(ASM5, methodVisitor, access, name, desc); this.classId = classId; this.classLineNumbers = classLineNumbers; this.methodLineNumbers = methodLineNumbers; this.branchPoints = branchPoints; this.oneTimeLines = oneTimeLines; this.access = access; this.name = name; this.useLocalVariables = reportLoad <= maxLocalVariableReportLoad; this.profile = CoverageClassVisitor.this.profile && !"<clinit>".equals(name); } // Maps absolute line number to local variable index private Map<Integer, Integer> lineNumberLocalVariables = new TreeMap<Integer, Integer>(); private Map<Integer, Integer> beforeBranchPointLocalVariables = new TreeMap<Integer, Integer>(); private Map<Integer, Integer> afterBranchPointLocalVariables = new TreeMap<Integer, Integer>(); public Label before; public Label handler; int insIdx = 0; @Override public void visitCode() { super.visitCode(); insIdx = 0; if(name.equals("<clinit>") && (access & ACC_STATIC) != 0) { staticInjected = true; visitFetchRevocCounter(mv); } if(trackLines) { if(useLocalVariables) { initializeLineNumberLocalVariables(); multiMethodCursorLocalVariable = newLocal(Type.INT_TYPE); } else { initializeLineNumerArrayLocalVariable(); } } if(trackBranches) { if(useLocalVariables) { initializeBranchPointLocalVariables(); } else { initializeBranchPointArrayLocalVariable(); } } if(profile) { mv.visitLdcInsn((long)classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerMethodEnter", "(J)Lorg/kantega/revoc/registry/Registry$FrameMap;"); mv.visitVarInsn(ASTORE, frameMapLocalVariable = newLocal(Type.getType(Registry.FrameMap.class))); initalizeProfilingLocalVariables(); } if(methodLineNumbers.size() == 1) { mv.visitLdcInsn((long) classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerOneLineVisited", "(J)V"); }else if(methodLineNumbers.size() > 1 ) { mv.visitLdcInsn((long)classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "getThreadLocalBufferInc", "(J)Lorg/kantega/revoc/registry/ThreadLocalBuffer;"); threadBufferLocal = newLocal(Type.getType(ThreadLocalBuffer.class)); mv.visitVarInsn(ASTORE, threadBufferLocal); } if (name.equals("<init>")) { constructor = true; } else { startTryBlock(); } } private void startTryBlock() { before = new Label(); handler = new Label(); mv.visitLabel(before); } private void initalizeProfilingLocalVariables() { if(profileTime) { nanoTime(); mv.visitVarInsn(LSTORE, startTimeLocalVariable = newLocal(Type.getType("J"))); } //mv.visitInsn(LCONST_0); //mv.visitVarInsn(LSTORE, totalWaitTimeLocalVariable = newLocal(Type.getType("J"))); //mv.visitInsn(LCONST_0); //mv.visitVarInsn(LSTORE, waitTimeLocalVariable = newLocal(Type.getType("J"))); } private void nanoTime() { mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J"); //mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "time", "J"); //mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "nanotime", "J"); //mv.visitInsn(Opcodes.LCONST_0); } private void initializeBranchPointArrayLocalVariable() { visitIntConstantInstruction(branchPoints.size()); mv.visitIntInsn(NEWARRAY, T_INT); mv.visitVarInsn(ASTORE, beforeBranchPointsLocalVariable = newLocal(Type.getType("[I"))); visitIntConstantInstruction(branchPoints.size()); mv.visitIntInsn(NEWARRAY, T_INT); mv.visitVarInsn(ASTORE, afterBranchPointsLocalVariable = newLocal(Type.getType("[I"))); } private void initializeLineNumerArrayLocalVariable() { visitIntConstantInstruction(methodLineNumbers.size()); mv.visitIntInsn(NEWARRAY, T_INT); mv.visitVarInsn(ASTORE, lineVisitsLocalVariable = newLocal(Type.getType("[I"))); if(trackTime) { visitIntConstantInstruction(methodLineNumbers.size()); mv.visitIntInsn(NEWARRAY, T_LONG); mv.visitVarInsn(ASTORE, timeVisitsLocalVariable = newLocal(Type.getType("[J"))); } } private void initializeBranchPointLocalVariables() { for (int branchIndex : branchPoints.keySet()) { { mv.visitInsn(ICONST_0); int local = newLocal(Type.INT_TYPE); beforeBranchPointLocalVariables.put(branchIndex, local); mv.visitVarInsn(ISTORE, local); } { mv.visitInsn(ICONST_0); int local = newLocal(Type.INT_TYPE); afterBranchPointLocalVariables.put(branchIndex, local); mv.visitVarInsn(ISTORE, local); } } } private void initializeLineNumberLocalVariables() { if(methodLineNumbers.size() > 1) { for (int lineNumber : methodLineNumbers.keySet()) { { visitIntConstantInstruction(-1); int local = newLocal(Type.INT_TYPE); lineNumberLocalVariables.put(lineNumber, local); mv.visitVarInsn(ISTORE, local); } } } } @Override public void visitLineNumber(int lineNumber, Label label) { insIdx++; mv.visitLineNumber(lineNumber, label); if(trackLines) { if(useLocalVariables) { if(methodLineNumbers.size() > 1) { mv.visitIincInsn(lineNumberLocalVariables.get(lineNumber), 1); } } else { { mv.visitVarInsn(ALOAD, lineVisitsLocalVariable); visitIntConstantInstruction(methodLineNumbers.get(lineNumber)); mv.visitInsn(DUP2); mv.visitInsn(IALOAD); mv.visitInsn(ICONST_1); mv.visitInsn(IADD); mv.visitInsn(IASTORE); } if(trackTime) { mv.visitVarInsn(ALOAD, timeVisitsLocalVariable); visitIntConstantInstruction(methodLineNumbers.get(lineNumber)); mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "time", "J"); mv.visitInsn(LASTORE); } } } } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { insIdx++; /* if(profile && isWaitMethod(opcode, owner, name, desc)) { nanoTime(); mv.visitVarInsn(LSTORE, waitTimeLocalVariable); } */ super.visitMethodInsn(opcode, owner, name, desc, itf); if(constructor && name.equals("<init>")) { constructor = false; startTryBlock(); } /* if(profile && isWaitMethod(opcode, owner, name, desc)) { nanoTime(); mv.visitVarInsn(LLOAD, waitTimeLocalVariable); mv.visitInsn(LSUB); mv.visitVarInsn(LLOAD, totalWaitTimeLocalVariable); mv.visitInsn(LADD); mv.visitVarInsn(LSTORE, totalWaitTimeLocalVariable); } */ if(trackTime) { //updateTime(); } } private boolean isWaitMethod(int opcode, String owner, String name, String desc) { return opcode == INVOKEVIRTUAL && "java/lang/Thread".equals(owner) && "join".equals(name) && "()V".equals(desc); } @Override public void visitInsn(int i) { if (trackTime && i >= IRETURN && i <= RETURN) { //updateTime(); } super.visitInsn(i); insIdx++; } private void updateTime() { if(trackTime) { mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "time", "J"); mv.visitVarInsn(LSTORE, timeLocal); } } /** * Visit a BIPUSH, SIPUSH or LDC instruction based on the size of num * * @param num the int constant to put on the stack */ private void visitIntConstantInstruction(int num) { if (num <= 5) { mv.visitInsn(ICONST_0 + num); } else if (num < 128) { mv.visitIntInsn(BIPUSH, num); } else if (num <= Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, num); } else { mv.visitLdcInsn(num); } } @Override public void visitMaxs(int maxStack, int maxLocals) { if(methodLineNumbers.size() > 1) { mv.visitTryCatchBlock(before, handler, handler, null); mv.visitLabel(handler); generateLineVisitRegistration(true, -1); mv.visitInsn(ATHROW); } mv.visitMaxs(maxStack, maxLocals + lineNumberLocalVariables.size() + beforeBranchPointLocalVariables.size() + afterBranchPointLocalVariables.size()); } @Override protected void onMethodExit(int opcode) { int myIndex = insIdx; if (opcode != ATHROW && methodLineNumbers.size() > 1) { generateLineVisitRegistration(false, myIndex); } } @Override public void visitJumpInsn(int i, Label label) { insIdx++; if (!trackBranches || i == Opcodes.GOTO || i == Opcodes.JSR ) { super.visitJumpInsn(i, label); } else { int index = methodJumpIndex; if(useLocalVariables) { { int local = beforeBranchPointLocalVariables.get(index); mv.visitIincInsn(local, 1); } super.visitJumpInsn(i, label); { int local = afterBranchPointLocalVariables.get(index); mv.visitIincInsn(local, 1); } } else { { mv.visitVarInsn(ALOAD, beforeBranchPointsLocalVariable); visitIntConstantInstruction(index); mv.visitInsn(DUP2); mv.visitInsn(IALOAD); mv.visitInsn(ICONST_1); mv.visitInsn(IADD); mv.visitInsn(IASTORE); } super.visitJumpInsn(i, label); { mv.visitVarInsn(ALOAD, afterBranchPointsLocalVariable); visitIntConstantInstruction(index); mv.visitInsn(DUP2); mv.visitInsn(IALOAD); mv.visitInsn(ICONST_1); mv.visitInsn(IADD); mv.visitInsn(IASTORE); } } methodJumpIndex++; } } private void generateLineVisitRegistration(boolean isCatchBlock, int myIndex) { if(profile) { mv.visitVarInsn(ALOAD, frameMapLocalVariable); if(profileTime) { nanoTime(); mv.visitVarInsn(LLOAD, startTimeLocalVariable); //mv.visitVarInsn(LLOAD, totalWaitTimeLocalVariable); mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerMethodExit", "(Lorg/kantega/revoc/registry/Registry$Frame;JJ)V"); } else { mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerMethodExit", "(Lorg/kantega/revoc/registry/Registry$Frame;)V"); } } // Get the int[] for this class if(trackLines) { { if(trackTime) { /* mv.visitFieldInsn(GETSTATIC, "org/kantega/revoc/registry/Registry", "lineTimes", "[Ljava/util/concurrent/atomic/AtomicLongArray;"); visitIntConstantInstruction(classId); mv.visitInsn(AALOAD); */ } } if(useLocalVariables) { if(methodLineNumbers.size() != 1) { mv.visitVarInsn(ALOAD, threadBufferLocal); mv.visitLdcInsn(((long) classId << 32 | (long) methodNames.size())); long initialMask = 0; if(!isCatchBlock) { for (Integer lineNumber : lineNumberLocalVariables.keySet()) { int lineIndex = methodLineNumbers.get(lineNumber); if (lineIndex < 64) { if (oneTimeLines.mustHaveRun(myIndex, lineIndex)) { initialMask |= (1l << lineIndex); } } } } mv.visitLdcInsn(initialMask); for (Integer lineNumber : lineNumberLocalVariables.keySet()) { int lineIndex = methodLineNumbers.get(lineNumber); if(lineIndex >= 64) { continue; } if(isCatchBlock) { continue; } if(oneTimeLines.cantHaveRun(myIndex, lineIndex)) { continue; } if(oneTimeLines.mustHaveRun(myIndex, lineIndex)) { continue; } mv.visitVarInsn(ILOAD, lineNumberLocalVariables.get(lineNumber)); mv.visitInsn(ICONST_M1); Label after = new Label(); mv.visitJumpInsn(IF_ICMPEQ, after); mv.visitLdcInsn(1l << lineIndex); mv.visitInsn(LOR); mv.visitLabel(after); } visitIntConstantInstruction(Math.min(64, lineNumberLocalVariables.size())); mv.visitMethodInsn(INVOKEVIRTUAL, "org/kantega/revoc/registry/ThreadLocalBuffer", "visitMultiMethod", "(JJI)I"); mv.visitVarInsn(ISTORE, multiMethodCursorLocalVariable); for (Integer lineNumber : lineNumberLocalVariables.keySet()) { int lineIndex = methodLineNumbers.get(lineNumber); if (isCatchBlock || !oneTimeLines.mustHaveRunOnce(myIndex, lineIndex)) { if (lineIndex < 64) { mv.visitVarInsn(ALOAD, threadBufferLocal); mv.visitVarInsn(ILOAD, multiMethodCursorLocalVariable); if(!isCatchBlock && oneTimeLines.cantHaveRun(myIndex, lineIndex)) { mv.visitInsn(ICONST_M1); } else { mv.visitVarInsn(ILOAD, lineNumberLocalVariables.get(lineNumber)); } visitIntConstantInstruction(lineIndex); mv.visitLdcInsn((long) classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKEVIRTUAL, "org/kantega/revoc/registry/ThreadLocalBuffer", "visitLine", "(IIIJ)V"); } } } for (Integer lineNumber : lineNumberLocalVariables.keySet()) { int lineIndex = methodLineNumbers.get(lineNumber); if(lineIndex >= 64) { mv.visitVarInsn(ALOAD, threadBufferLocal); mv.visitVarInsn(ILOAD, lineNumberLocalVariables.get(lineNumber)); visitIntConstantInstruction(lineIndex); mv.visitLdcInsn((long) classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKEVIRTUAL, "org/kantega/revoc/registry/ThreadLocalBuffer", "visitLine", "(IIJ)V"); } } mv.visitVarInsn(ALOAD, threadBufferLocal); mv.visitLdcInsn((long) classId << 32 | (long) methodNames.size()); mv.visitMethodInsn(INVOKEVIRTUAL, "org/kantega/revoc/registry/ThreadLocalBuffer", "popStack", "(J)V"); } } else { mv.visitFieldInsn(GETSTATIC, className, "revoc_counters", "Ljava/util/concurrent/atomic/AtomicLongArray;"); if(trackTime) { mv.visitFieldInsn(GETSTATIC, className, "revoc_times", "Ljava/util/concurrent/atomic/AtomicLongArray;"); } mv.visitVarInsn(ALOAD, lineVisitsLocalVariable); if(trackTime) { mv.visitVarInsn(ALOAD, timeVisitsLocalVariable); } visitIntConstantInstruction(classLineNumbers.get(methodLineNumbers.keySet().iterator().next())); if(trackTime) { mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerLineTimeVisitedArray", "(Ljava/util/concurrent/atomic/AtomicLongArray;Ljava/util/concurrent/atomic/AtomicLongArray;[I[JI)V"); } else { mv.visitMethodInsn(INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerLineVisitedArray", "(Ljava/util/concurrent/atomic/AtomicLongArray;[II)V"); } } } if(trackBranches && !branchPoints.isEmpty()) { if(useLocalVariables) { for(Integer index :branchPoints.keySet()) { visitIntConstantInstruction(classId); visitIntConstantInstruction(branchPoints.get(index)); mv.visitVarInsn(ILOAD, beforeBranchPointLocalVariables.get(index)); mv.visitVarInsn(ILOAD, afterBranchPointLocalVariables.get(index)); mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerBranchPointVisits", "(IIII)V"); } } else { visitIntConstantInstruction(classId); mv.visitVarInsn(ALOAD, beforeBranchPointsLocalVariable); mv.visitVarInsn(ALOAD, afterBranchPointsLocalVariable); visitIntConstantInstruction(branchPoints.get(branchPoints.keySet().iterator().next())); mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/kantega/revoc/registry/Registry", "registerBranchPointVisitsArray", "(I[I[II)V"); } } } @Override public void visitLabel(Label label) { insIdx++; super.visitLabel(label); } @Override public void visitVarInsn(int opcode, int var) { insIdx++; super.visitVarInsn(opcode, var); } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { insIdx++; super.visitFrame(type, nLocal, local, nStack, stack); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { insIdx++; super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitIntInsn(int opcode, int operand) { insIdx++; super.visitIntInsn(opcode, operand); } @Override public void visitLdcInsn(Object cst) { insIdx++; super.visitLdcInsn(cst); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { insIdx++; super.visitMultiANewArrayInsn(desc, dims); } @Override public void visitTypeInsn(int opcode, String type) { insIdx++; super.visitTypeInsn(opcode, type); } @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { insIdx++; super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } @Override public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { insIdx++; super.visitLookupSwitchInsn(dflt, keys, labels); } @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { insIdx++; super.visitTableSwitchInsn(min, max, dflt, labels); } } public BitSet getExistingLines() { return existingLines; } public int[] getLineIndexes() { Map<Integer, Integer> index2Line = new TreeMap<Integer, Integer>(); for (Integer lineNum : classLineNumbers.keySet()) { index2Line.put(classLineNumbers.get(lineNum), lineNum); } int[] lines = new int[index2Line.size()]; int c = 0; for (Integer index : index2Line.keySet()) { lines[c++] = index2Line.get(index); } return lines; } public List<BranchPoint> getBranchPoints() { return branchPoints; } public String getSource() { return source; } public String getClassName() { return className; } public boolean isInterface() { return (access & Opcodes.ACC_INTERFACE) != 0; } public boolean isEnum() { return (access & Opcodes.ACC_ENUM) != 0; } public List<String> getInnerClasses() { return innerClasses; } public List<String> getMethodNames() { return methodNames; } public List<String> getMethodDescs() { return methodDescs; } }