JaCoCo core-internal-flow包源码

jacoco有对类级别,方法级别,逻辑分支级别以及代码行级别做了专门的处理封装。具体的封装类在internal.analysis.flow

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(方法级别)。

ClassprobesAdapter

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);
        }
      }
    };
  }

可见类覆盖率字节码埋入实际上是对类中每一个方法和每一个逻辑分支做埋入,只要记录调用类中方法的覆盖代码行,自然类的覆盖就会被统计到。

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;

        }

    }

逻辑跳转的有switchif

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,那么指令值按顺序执行的,所以我们只要在开始的时候添加一个探针就好,只要探针指令执行了,那么下面的指令一定会被执行的,除非有了跳转逻辑。因此我们只要在每一个跳转的开始和结束添加探针就好,就可以完全实现统计代码块的覆盖,而没有必要每一行都要植入探针。

发布了406 篇原创文章 · 获赞 1032 · 访问量 51万+

猜你喜欢

转载自blog.csdn.net/qq_33589510/article/details/105249899
今日推荐