对类的植入锁定进行判断

几个可以对覆盖率跟踪的Java类定义进行instrument的API

public byte[] instrument(final ClassReader reader) {final ClassWriter writer = new ClassWriter(reader, 0) {@Overrideprotected String getCommonSuperClass(final String type1,final String type2) {throw new IllegalStateException();}};final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory.createFor(reader, accessorGenerator);final ClassVisitor visitor = new ClassProbesAdapter(new ClassInstrumenter(strategy, writer), true);reader.accept(visitor, ClassReader.EXPAND_FRAMES);return writer.toByteArray();}public byte[] instrument(final byte[] buffer, final String name)throws IOException {try {return instrument(new ClassReader(buffer));} catch (final RuntimeException e) {throw instrumentError(name, e);}}public byte[] instrument(final InputStream input, final String name)throws IOException {final byte[] bytes;try {bytes = InputStreams.readFully(input);} catch (final IOException e) {throw instrumentError(name, e);}return instrument(bytes, name);}public void instrument(final InputStream input, final OutputStream output,final String name) throws IOException {output.write(instrument(input, name));}private IOException instrumentError(final String name,final Exception cause) {final IOException ex = new IOException(String.format("Error while instrumenting %s.", name));ex.initCause(cause);return ex;}
  • 执行流程图:

所以最终的出口在于最下面的instrument(input,output,string),下面是剩余部分代码:

public int instrumentAll(final InputStream input, final OutputStream output,final String name) throws IOException {final ContentTypeDetector detector;try {detector = new ContentTypeDetector(input);} catch (final IOException e) {throw instrumentError(name, e);}switch (detector.getType()) {case ContentTypeDetector.CLASSFILE:instrument(detector.getInputStream(), output, name);return 1;case ContentTypeDetector.ZIPFILE:return instrumentZip(detector.getInputStream(), output, name);case ContentTypeDetector.GZFILE:return instrumentGzip(detector.getInputStream(), output, name);case ContentTypeDetector.PACK200FILE:return instrumentPack200(detector.getInputStream(), output, name);default:copy(detector.getInputStream(), output, name);return 0;}}private int instrumentZip(final InputStream input,final OutputStream output, final String name) throws IOException {final ZipInputStream zipin = new ZipInputStream(input);final ZipOutputStream zipout = new ZipOutputStream(output);ZipEntry entry;int count = 0;while ((entry = nextEntry(zipin, name)) != null) {final String entryName = entry.getName();if (signatureRemover.removeEntry(entryName)) {continue;}zipout.putNextEntry(new ZipEntry(entryName));if (!signatureRemover.filterEntry(entryName, zipin, zipout)) {count += instrumentAll(zipin, zipout, name + "@" + entryName);}zipout.closeEntry();}zipout.finish();return count;}private ZipEntry nextEntry(final ZipInputStream input,final String location) throws IOException {try {return input.getNextEntry();} catch (final IOException e) {throw instrumentError(location, e);}}private int instrumentGzip(final InputStream input,final OutputStream output, final String name) throws IOException {final GZIPInputStream gzipInputStream;try {gzipInputStream = new GZIPInputStream(input);} catch (final IOException e) {throw instrumentError(name, e);}final GZIPOutputStream gzout = new GZIPOutputStream(output);final int count = instrumentAll(gzipInputStream, gzout, name);gzout.finish();return count;}private int instrumentPack200(final InputStream input,final OutputStream output, final String name) throws IOException {final InputStream unpackedInput;try {unpackedInput = Pack200Streams.unpack(input);} catch (final IOException e) {throw instrumentError(name, e);}final ByteArrayOutputStream buffer = new ByteArrayOutputStream();final int count = instrumentAll(unpackedInput, buffer, name);Pack200Streams.pack(buffer.toByteArray(), output);return count;}private void copy(final InputStream input, final OutputStream output,final String name) throws IOException {final byte[] buffer = new byte[1024];int len;while ((len = read(input, buffer, name)) != -1) {output.write(buffer, 0, len);}}private int read(final InputStream input, final byte[] buffer,final String name) throws IOException {try {return input.read(buffer);} catch (final IOException e) {throw instrumentError(name, e);}}

核心关键是instrumentAll这个方法,根据文件包是class还是zip,或者gz等,不同的加载方式。

ClassInstrumenter 类

适配器为了类覆盖率跟踪。

import org.jacoco.core.internal.flow.ClassProbesVisitor;
import org.jacoco.core.internal.flow.MethodProbesVisitor;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;/*** Adapter that instruments a class for coverage tracing.*/
public class ClassInstrumenter extends ClassProbesVisitor {private final IProbeArrayStrategy probeArrayStrategy;private String className;/*** Emits a instrumented version of this class to the given class visitor.* 向给定的class 访问者发出此类的instrumented版本** @param probeArrayStrategy*            该策略将用于访问探针数组* @param cv*            访问链中的下一位 delegate 将获得 instrumente 类*/public ClassInstrumenter(final IProbeArrayStrategy probeArrayStrategy,final ClassVisitor cv) {super(cv);this.probeArrayStrategy = probeArrayStrategy;}@Overridepublic void visit(final int version, final int access, final String name,final String signature, final String superName,final String[] interfaces) {this.className = name;super.visit(version, access, name, signature, superName, interfaces);}@Overridepublic FieldVisitor visitField(final int access, final String name,final String desc, final String signature, final Object value) {InstrSupport.assertNotInstrumented(name, className);return super.visitField(access, name, desc, signature, value);}@Overridepublic MethodProbesVisitor visitMethod(final int access, final String name,final String desc, final String signature,final String[] exceptions) {InstrSupport.assertNotInstrumented(name, className);final MethodVisitor mv = cv.visitMethod(access, name, desc, signature,exceptions);if (mv == null) {return null;}final MethodVisitor frameEliminator = new DuplicateFrameEliminator(mv);final ProbeInserter probeVariableInserter = new ProbeInserter(access,name, desc, frameEliminator, probeArrayStrategy);return new MethodInstrumenter(probeVariableInserter,probeVariableInserter);}@Overridepublic void visitTotalProbeCount(final int count) {probeArrayStrategy.addMembers(cv, count);}}

DuplicateFrameEliminator

消除了导致ASM创建无效类文件的连续 stackmap frames 定义。 当原始类文件在意外偏移处包含其他 stackmap frames 时,就会发生这种情况,某些使用ECJ编译的类文件就是这种情况。

ProbeInserter - 探针植入类


内部实用程序,用于将探针添加到方法的控制流中。

探针的代码只是将布尔数组的某个插槽设置为true。
另外,必须在方法开始时检索探针数组并将其存储在局部变量中。

构造方法

  • 创建一个新的ProbeInserter
 /**** @param access*            access flags of the adapted method* @param name*            the method's name* @param desc*            the method's descriptor* @param mv*            the method visitor to which this adapter delegates calls* @param arrayStrategy*            callback to create the code that retrieves the reference to*            the probe array*/

visitmax

探针代码的最大堆栈大小为3,这可以增加到原始堆栈大小,具体取决于探针位置。 访问者堆栈大小是绝对最大值,因为当堆栈大小为空时,访问者代码会在每种方法的开头插入。

 @Overridepublic void visitMaxs(final int maxStack, final int maxLocals) {final int increasedStack = Math.max(maxStack + 3, accessorStackSize);mv.visitMaxs(increasedStack, maxLocals + 1);}

insertProbe - 插入具有给定id的探针


visitIincInsn - 访问 IINC 指令

visitLocalVariable - 访问局部变量声明

Visits a local variable declaration.

    private void visitInsn() {final Instruction insn = newInstruction(currentNode, currentLine);nodeToInstruction.put(currentNode,insn);instructions.add(insn);if (lastInsn != null) {insn.setPredecessor(lastInsn, 0);}final int labelCount =currentLabel.size();if (labelCount > 0) {for (int i = labelCount; --i >=0;) {LabelInfo.setInstruction(currentLabel.get(i),insn);}currentLabel.clear();}lastInsn = insn;}

大致就是,在对应字节码的执行入口和跳转入口处,置放 probe,是一个数值(该数值和probe id有关),入栈后加1,则记录一次执行

  • 所有放入的探针对应一个boolean[]
  • 探针入栈之后,那么boolean[] 对应的位置变成true,记录执行了。

InstrSupport 类原理

Constants and utilities for byte code instrumentation
字节码检测的常量和实用程序。

属性

public static final int ASM_API_VERSION = Opcodes.ASM7;
  • 接口初始化方法的名称。
static final String CLINIT_NAME = "<clinit>";

存储类的boolean[]数组的coverage信息的字段的数据类型

public static final String DATAFIELD_DESC = "[Z";

初始化方法的名称。

public static final String INITMETHOD_NAME = "$jacocoInit";

初始化方法的描述符。

public static final String INITMETHOD_DESC = "()[Z";
 /*** Access modifiers of the initialization method.*/public static final int INITMETHOD_ACC = Opcodes.ACC_SYNTHETIC| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;

needsFrames

 /*** 确定给定的 class 文件版本是否需要 stackmap frames.** @param version*            class file version* @return <code>true</code> if frames are required*/public static boolean needsFrames(final int version) {// consider major version only (due to 1.1 anomaly)return (version & 0xFFFF) >= Opcodes.V1_6;}

classReaderFor

 /*** Creates a {@link ClassReader} instance for given bytes of class even if* its version not yet supported by ASM.** @param b*            bytes of class* @return {@link ClassReader}*/public static ClassReader classReaderFor(final byte[] b) {final int originalVersion = getMajorVersion(b);if (originalVersion == Opcodes.V14 + 1) {// temporarily downgrade version to bypass check in ASMsetMajorVersion(Opcodes.V14, b);}final ClassReader classReader = new ClassReader(b);setMajorVersion(originalVersion, b);return classReader;}

assertNotInstrumented

Ensures that the given member does not correspond to a internal member created by the instrumentation process. This would mean that the class is already instrumented.
确保给定成员与 instrumentation 过程创建的内部成员不对应。 这意味着该类已经被检测。

push

Generates the instruction to push the given int value on the stack.

Implementation taken from org.objectweb.asm.commons.GeneratorAdapter#push(int)

生成指令以将给定的int值压入堆栈。

取自org.objectweb.asm.commons.GeneratorAdapter#push(int)的实现

Push是用来对于不同的变量值入栈的不同方式,当int取值

  • -1 ~ 5,JVM采用iconst指令将常量压入栈中
  • -128 ~ 127,bipush
  • -32768 ~ 32767,sipush
  • -2147483648~2147483647,ldc

主要作为单例的使用,ClassInstrumenter, ClassAnalyzer调用InstrSupport

ClassAnalyzer 类调用如下:

public static final String DATAFIELD_DESC = "[Z";// === Init Method ===/*** Name of the initialization method.*/public static final String INITMETHOD_NAME = "$jacocoInit";/*** Descriptor of the initialization method.*/public static final String INITMETHOD_DESC = "()[Z";public static void assertNotInstrumented(final String member,final String owner) throws IllegalStateException {if (member.equals(DATAFIELD_NAME) || member.equals(INITMETHOD_NAME)) {throw new IllegalStateException(format("Class %s is already instrumented.", owner));}}

IProbeArrayStrategy

检索类型内每个方法的探针数组实例的策略。 这种抽象是必需的,因为我们需要根据所检测的类型是类还是接口来遵循不同的策略。

storeInstance

 /*** Creates code that stores the probe array instance in the given variable.** @param mv*            visitor to create code* @param clinit*            true in case of {@code <clinit>} method* @param variable*            variable index to store probe array to* @return maximum stack size required by the generated code*/int storeInstance(MethodVisitor mv, boolean clinit, int variable);

创建将探针数组实例存储在给定变量中的代码。

Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析相关推荐

  1. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  2. java.lang 源码剖析_java.lang.Void类源码解析

    在一次源码查看ThreadGroup的时候,看到一段代码,为以下: /* * @throws NullPointerException if the parent argument is {@code ...

  3. abp vnext2.0核心组件之.Net Core默认DI组件切换到AutoFac源码解析

    老版Abp对Castle的严重依赖在vnext中已经得到了解决,vnext中DI容器可以任意更换,为了实现这个功能,底层架构相较于老版abp,可以说是进行了高度重构.当然这得益于.Net Core的D ...

  4. JAVA轮播器_Android 图片轮播器的实现及源码解析

    在很多产品,尤其是电商类社区内的网页或者app中,我们经常会看到一个图片轮播墙,一页一页的广告/活动/商品介绍每隔一段时间就切换到下一张.那在安卓中我们该如何实现图片轮播器呢?面对自定义样式.自定义图 ...

  5. Java FileReader InputStreamReader类源码解析

    FileReader 前面介绍FileInputStream的时候提到过,它是从文件读取字节,如果要从文件读取字符的话可以使用FileReader.FileReader是可以便利读取字符文件的类,构造 ...

  6. Java集合---Arrays类源码解析

    一.Arrays.sort()数组排序 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类型: ...

  7. 深入理解Spark 2.1 Core (十三):sparkEnv类源码分析

    sparkEnv为运行的Spark实例(master,worker,executor等)持有运行环境相关的对象,sparkenv管理serializer, Akka actor system, blo ...

  8. .Net Core 中间件之主机地址过滤(HostFiltering)源码解析

    一.介绍 主机地址过滤中间件相当于一个白名单,标记哪些主机地址能访问接口. 二.使用 新建WebAPI项目,修改Startup中的代码段如下所示.下面表示允许主机名为"localhost&q ...

  9. Java Optional类源码解析和用法

    个人网站:http://xiaocaoshare.com/ 要求:jdk1.8以上 Optional类是一个可以为null的容器对象.如果值存在则isPresent()方法会返回true,调用get( ...

最新文章

  1. OSD的主要实现方法和类型(转)
  2. JavaScript事件详解-jQuery的事件实现(三)
  3. 计算是计算机科学独有的方法,大学计算机基础教学中的计算思维培养.doc
  4. rcu_assign_pointer、rcu_dereference、ACCESS_ONCE
  5. php判断是否是关联数组,php 关联数组判断是否为空
  6. Palindromic Numbers LightOJ - 1205 数位dp 求回文数
  7. 面向空天地一体多接入的融合6G网络架构展望
  8. [顶]ORACLE PL/SQL编程详解之二:PL/SQL块结构和组成元素(为山九仞,岂一日之功)...
  9. jquery eaayui 学习(四)datagrid
  10. 滚动模式_违停车辆有可能被强制拖车!嘉兴交警开启滚动式兵团化作战模式
  11. Elasticsearch6.3.0环境安装
  12. GitHub的提醒邮件改进
  13. c语言课程设计报告内容,c语言课程设计报告
  14. 通过dSYM文件分析crash日志
  15. javaweb day14
  16. maven atuo import
  17. Apple苹果EDI案例
  18. 基于VC++的MFC类库实现的简单FTP客户端
  19. 钉钉机器人+Mac定时Launchctl
  20. 如何理解电容器容抗等效

热门文章

  1. C语言 荷兰国旗解法
  2. 像素和厘米怎么换算_临帖要像?计算机“算法”精确你的每一个像素!惊呆书法圈...
  3. Python 爬虫:scrapy 没有crawl 命令
  4. 最大报文段长度——MSS
  5. 网站备案靠谱吗_代理网站备案是怎么回事,代理网站备案靠谱吗? | 帮助信息-动天数据...
  6. JSP页面显示本地图片
  7. 邮储银行以小换大 再论银行能否去IOE
  8. 掘金是不是对php有意见,富贵教你用PHP爬取掘金文章 | 码农网
  9. matlab非线性约束条件,MATLAB 非线性规划及非线性约束条件求解
  10. 金仓数据库 KingbaseES 插件参考手册 plsql_plprofiler