JaCoCo core.internal.flow包源码

举报
JavaEdge 发表于 2021/06/04 01:21:34 2021/06/04
【摘要】 jacoco有对类级别,方法级别,逻辑分支级别以及代码行级别做了专门的处理封装。具体的封装类在internal.analysis.flow 1 IFrame 接口 import org.objectweb.asm.MethodVisitor; /** * 当前的 stackmap frame 内容的表示 */ public interface IFrame {...

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

涉及的类分别是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;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

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

3 ClassProbesVisitor 抽象类

具有附加方法的 ClassVisitor,可获取每种方法的探针插入信息

/**
 * 当访问一个方法时,我们需要一个 MethodProbesVisitor 来处理该方法的探测
 */
@Override
public abstract MethodProbesVisitor visitMethod(int access, String name,
		String desc, String signature, String[] exceptions);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136

退出方法是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); } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

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;

……

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

如果没有跳转指令 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);
		}
	}

}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

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;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

文章来源: javaedge.blog.csdn.net,作者:JavaEdge.,版权归原作者所有,如需转载,请联系作者。

原文链接:javaedge.blog.csdn.net/article/details/105249899

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。