jacoco有对类级别,方法级别,逻辑分支级别以及代码行级别做了专门的处理封装。具体的封装类在internal.analysis.flow
1 IFrame 接口
import org.objectweb.asm.MethodVisitor;
/**
* 当前的 stackmap frame 内容的表示
*/
public interface IFrame {
/**
* 向给定的访问者发出具有当前内容的 frame 事件
*
* @param mv 向该方法发出 frame 事件的method visitor
*/
void accept(MethodVisitor mv);
}
涉及的类分别是ClassprobesAdapter.java(类级别),Instruction.java(指令级别),LabelFlowAnalysis.java(逻辑分支级别)和MethodProbesAdapter.java(方法级别)。
2 ClassprobesAdapter
一个 org.objectweb.asm.ClassVisitor,它为每个方法计算探针.
属性
private static final MethodProbesVisitor EMPTY_METHOD_PROBES_VISITOR = new MethodProbesVisitor() {
};
// The class visitor to which this visitor must delegate method calls. May be null
// 委托的实例,该访问者必须委派方法调用的类访问者。 可能为null
private final ClassProbesVisitor cv;
// 如果为true,跟踪并提供 stackmap frames
private final boolean trackFrames;
private int counter = 0;
private String name;
visitMethod
publicfinal MethodVisitor visitMethod(intaccess, String name, String desc, String signature, String[] exceptions)
{
MethodProbesVisitor mv =this.cv.visitMethod(access, name, desc, signature, exceptions);
MethodProbesVisitor methodProbes;
final MethodProbesVisitor methodProbes;
if (mv == null) {
methodProbes =EMPTY_METHOD_PROBES_VISITOR;
} else {
methodProbes = mv;
}
new MethodSanitizer(null, access, name,desc, signature, exceptions)
{
public void visitEnd()
{
super.visitEnd();
LabelFlowAnalyzer.markLabels(this);
MethodProbesAdapter probesAdapter = newMethodProbesAdapter(methodProbes, ClassProbesAdapter.this);
if(ClassProbesAdapter.this.trackFrames)
{
AnalyzerAdapter analyzer = new AnalyzerAdapter(ClassProbesAdapter.this.name,this.access, this.name, this.desc, probesAdapter); probesAdapter.setAnalyzer(analyzer);
accept(analyzer);
}
else
{
accept(probesAdapter);
}
}
};
}
可见类覆盖率字节码埋入实际上是对类中每一个方法和每一个逻辑分支做埋入,只要记录调用类中方法的覆盖代码行,自然类的覆盖就会被统计到。
3 ClassProbesVisitor 抽象类
具有附加方法的 ClassVisitor,可获取每种方法的探针插入信息
/**
* 当访问一个方法时,我们需要一个 MethodProbesVisitor 来处理该方法的探测
*/
@Override
public abstract MethodProbesVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions);
4 MethodProbesAdapter
适配器,用于创建其他访问者事件以将探针插入到方法中。
@Override
public void visitLabel(final Label label) {
if (LabelInfo.needsProbe(label)) {
if(tryCatchProbeLabels.containsKey(label)) {
probesVisitor.visitLabel(tryCatchProbeLabels.get(label));
}
probesVisitor.visitProbe(idGenerator.nextId());
}
probesVisitor.visitLabel(label);
}
@Override
public void visitInsn(final int opcode) {
switch (opcode) {
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
probesVisitor.visitInsnWithProbe(opcode,idGenerator.nextId());
break;
default:
probesVisitor.visitInsn(opcode);
break;
}
}
@Override
public void visitJumpInsn(final int opcode, final Label label) {
if (LabelInfo.isMultiTarget(label)) {
probesVisitor.visitJumpInsnWithProbe(opcode,label,
idGenerator.nextId(), frame(jumpPopCount(opcode)));
} else {
probesVisitor.visitJumpInsn(opcode,label);
}
}
private int jumpPopCount(final int opcode) {
switch (opcode) {
case Opcodes.GOTO:
return 0;
case Opcodes.IFEQ:
case Opcodes.IFNE:
case Opcodes.IFLT:
case Opcodes.IFGE:
case Opcodes.IFGT:
case Opcodes.IFLE:
case Opcodes.IFNULL:
case Opcodes.IFNONNULL:
return 1;
default: // IF_CMPxx and IF_ACMPxx
return 2;
}
}
@Override
public void visitLookupSwitchInsn(final Label dflt, final int[]keys,
final Label[] labels) {
if (markLabels(dflt, labels)) {
probesVisitor.visitLookupSwitchInsnWithProbes(dflt,keys, labels,
frame(1));
} else {
probesVisitor.visitLookupSwitchInsn(dflt,keys, labels);
}
}
@Override
public void visitTableSwitchInsn(final int min, final int max,
final Label dflt, final Label...labels) {
if (markLabels(dflt, labels)) {
probesVisitor.visitTableSwitchInsnWithProbes(min,max, dflt,
labels, frame(1));
} else {
probesVisitor.visitTableSwitchInsn(min,max, dflt, labels);
}
}
在MethodProbesAdapter中明显看到字节码指令信息,对于一个方法的进入,jvm中是一个方法栈的创建,入口指令是入栈指令,退出是return:
privateint jumpPopCount(finalint opcode) {
switch (opcode) {
case Opcodes.GOTO:
return0;
caseOpcodes.IFEQ:
caseOpcodes.IFNE:
caseOpcodes.IFLT:
caseOpcodes.IFGE:
caseOpcodes.IFGT:
caseOpcodes.IFLE:
caseOpcodes.IFNULL:
caseOpcodes.IFNONNULL:
return1;
default:// IF_CMPxx and IF_ACMPxx
return2;
}
}
退出方法是return 指令:
publicvoid visitInsn(finalint opcode) {
switch (opcode) {
case Opcodes.IRETURN:
caseOpcodes.LRETURN:
caseOpcodes.FRETURN:
caseOpcodes.DRETURN:
caseOpcodes.ARETURN:
caseOpcodes.RETURN:
caseOpcodes.ATHROW:
probesVisitor.visitInsnWithProbe(opcode,idGenerator.nextId());
break;
default:
probesVisitor.visitInsn(opcode);
break;
}
}
逻辑跳转的有switch,if
publicvoid visitTableSwitchInsn(finalint min, final int max,
final Label dflt, final Label...labels) {
if (markLabels(dflt, labels)) {
probesVisitor.visitTableSwitchInsnWithProbes(min,max, dflt,
labels, frame(1));
} else {
probesVisitor.visitTableSwitchInsn(min,max, dflt, labels);
}
}
If分支:
case Opcodes.GOTO:
return0;
caseOpcodes.IFEQ:
caseOpcodes.IFNE:
caseOpcodes.IFLT:
caseOpcodes.IFGE:
caseOpcodes.IFGT:
caseOpcodes.IFLE:
caseOpcodes.IFNULL:
caseOpcodes.IFNONNULL:
return1;
default:// IF_CMPxx and IF_ACMPxx
return2;
}
LabelFlowAnalysis主要实现代码:
@Override
public void visitJumpInsn(final int opcode, final Label label) {
LabelInfo.setTarget(label);
if (opcode == Opcodes.JSR) {
thrownew AssertionError("Subroutines not supported.");
}
successor = opcode != Opcodes.GOTO;
first = false;
}
@Override
public void visitLabel(final Label label) {
if (first) {
LabelInfo.setTarget(label);
}
if (successor) {
LabelInfo.setSuccessor(label);
}
}
@Override
public void visitLineNumber(final int line, final Label start) {
lineStart = start;
}
@Override
public void visitTableSwitchInsn(final int min, final int max,
final Label dflt, final Label...labels) {
visitSwitchInsn(dflt, labels);
}
@Override
public void visitLookupSwitchInsn(final Label dflt, final int[]keys,
final Label[] labels) {
visitSwitchInsn(dflt, labels);
}
@Override
public void visitInsn(final int opcode) {
switch (opcode) {
case Opcodes.RET:
throw new AssertionError("Subroutinesnot supported.");
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
case Opcodes.RETURN:
case Opcodes.ATHROW:
successor = false;
break;
default:
successor = true;
break;
}
first = false;
}
首先要知道对于一串指令比如:
iLoad A;
iLoad B;
Add A,B;
iStore;
……
如果没有跳转指令 GOTO LABEL或者jump,那么指令值按顺序执行的,所以我们只要在开始的时候添加一个探针就好,只要探针指令执行了,那么下面的指令一定会被执行的,除非有了跳转逻辑。因此我们只要在每一个跳转的开始和结束添加探针就好,就可以完全实现统计代码块的覆盖,而没有必要每一行都要植入探针。
5 MethodInstrumenter
- 这个方法适配器根据 MethodProbesVisitor 事件的请求插入探针。
6 MethodSanitizer
此 method visitor 修复了Java字节码的两个潜在问题:
-
- 通过内联从Java 6开始不推荐使用的子例程来删除JSR / RET指令。RET语句使控制流分析变得复杂,因为未明确给出跳转目标
- 如果代码属性的行号和局部变量名称指向某些工具创建的无效偏移量,则将其删除。 用ASM类文件写出此类无效标签时,请勿再进行验证
class MethodSanitizer extends JSRInlinerAdapter {
MethodSanitizer(final MethodVisitor mv, final int access, final String name,
final String desc, final String signature,
final String[] exceptions) {
super(InstrSupport.ASM_API_VERSION, mv, access, name, desc, signature,
exceptions);
}
@Override
public void visitLocalVariable(final String name, final String desc,
final String signature, final Label start, final Label end,
final int index) {
// Here we rely on the usage of the info fields by the tree API. If the
// labels have been properly used before the info field contains a
// reference to the LabelNode, otherwise null.
if (start.info != null && end.info != null) {
super.visitLocalVariable(name, desc, signature, start, end, index);
}
}
@Override
public void visitLineNumber(final int line, final Label start) {
// Here we rely on the usage of the info fields by the tree API. If the
// labels have been properly used before the info field contains a
// reference to the LabelNode, otherwise null.
if (start.info != null) {
super.visitLineNumber(line, start);
}
}
}
LabelInfo
属性
/**
* Reserved ID for "no probe".
*/
public static final int NO_PROBE = -1;
private boolean target = false;
private boolean multiTarget = false;
private boolean successor = false;
private boolean methodInvocationLine = false;
private boolean done = false;
private int probeid = NO_PROBE;
private Label intermediate = null;
private Instruction instruction = null;
API
/**
* 确定给定标签是否需要在其之前插入探针。
*
* @param label label to test
* @return true if a probe should be inserted before
*/
public static boolean needsProbe(final Label label) {
final LabelInfo info = get(label);
return info != null && info.successor
&& (info.multiTarget || info.methodInvocationLine);
}