前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JaCoCo core.internal.flow包源码

JaCoCo core.internal.flow包源码

作者头像
JavaEdge
发布2020-05-26 17:02:25
6240
发布2020-05-26 17:02:25
举报
文章被收录于专栏:JavaEdgeJavaEdgeJavaEdge

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

附加到 Label#info 对象的数据容器,用于存储 flow 和特定 instrumentation 的信息。 该信息仅在特定情况下在本地有效。

属性

	/**
	 * 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);
	}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 IFrame 接口
  • 2 ClassprobesAdapter
    • 属性
      • visitMethod
      • 3 ClassProbesVisitor 抽象类
      • 4 MethodProbesAdapter
      • 5 MethodInstrumenter
      • 6 MethodSanitizer
      • LabelInfo
        • 属性
          • API
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档