Jacoco探针源码解析(0.8.5 版本)(下)

简介: Jacoco探针源码解析(0.8.5 版本)(下)

ClassInstrumenter

Adapter that instruments a class for coverage tracing.

适配器为类覆盖率跟踪。

@Override
  public 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);
  }
  @Override
  public 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);
  }

在jacoco对类和方法进行植入的时候,会


instrumenter - 对类的植入锁定进行判断

Several APIs to instrument Java class definitions for coverage tracing.

几个API可以对覆盖范围跟踪的Java类定义进行检测。

public byte[] instrument(final ClassReader reader) {
    final ClassWriter writer = new ClassWriter(reader, 0) {
      @Override
      protected 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) => instrument(input,string) => instrument([],string) => instrument(classreader)


所以最终的出口在于最下面的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等,不同的加载方式。

loadclass入口类:CoverageTransformer

public class CoverageTransformer
  implements ClassFileTransformer
{
  private static final String AGENT_PREFIX;
  private final Instrumenter instrumenter;
  private final IExceptionLogger logger;
  private final WildcardMatcher includes;
  private final WildcardMatcher excludes;
  private final WildcardMatcher exclClassloader;
  private final ClassFileDumper classFileDumper;
  private final boolean inclBootstrapClasses;
  private final boolean inclNoLocationClasses;
  static
  {
    String name = CoverageTransformer.class.getName();
    AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
  }
  public CoverageTransformer(IRuntime runtime, AgentOptions options, IExceptionLogger logger)
  {
    this.instrumenter = new Instrumenter(runtime);
    this.logger = logger;
    this.includes = new WildcardMatcher(toVMName(options.getIncludes()));
    this.excludes = new WildcardMatcher(toVMName(options.getExcludes()));
    this.exclClassloader = new WildcardMatcher(options.getExclClassloader());
    this.classFileDumper = new ClassFileDumper(options.getClassDumpDir());
    this.inclBootstrapClasses = options.getInclBootstrapClasses();
    this.inclNoLocationClasses = options.getInclNoLocationClasses();
  }
  public byte[] transform(ClassLoader loader, String classname, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
    throws IllegalClassFormatException
  {
    if (classBeingRedefined != null) {
      return null;
    }
    if (!filter(loader, classname, protectionDomain)) {
      return null;
    }
    try
    {
      this.classFileDumper.dump(classname, classfileBuffer);
      return this.instrumenter.instrument(classfileBuffer, classname);
    }
    catch (Exception ex)
    {
      IllegalClassFormatException wrapper = new IllegalClassFormatException(ex.getMessage());
      wrapper.initCause(ex);
      this.logger.logExeption(wrapper);
      throw wrapper;
    }
  }
  boolean filter(ClassLoader loader, String classname, ProtectionDomain protectionDomain)
  {
    if (loader == null)
    {
      if (!this.inclBootstrapClasses) {
        return false;
      }
    }
    else
    {
      if ((!this.inclNoLocationClasses) && (!hasSourceLocation(protectionDomain))) {
        return false;
      }
      if (this.exclClassloader.matches(loader.getClass().getName())) {
        return false;
      }
    }
    return (!classname.startsWith(AGENT_PREFIX)) && (this.includes.matches(classname)) && (!this.excludes.matches(classname));
  }
  private boolean hasSourceLocation(ProtectionDomain protectionDomain)
  {
    if (protectionDomain == null) {
      return false;
    }
    CodeSource codeSource = protectionDomain.getCodeSource();
    if (codeSource == null) {
      return false;
    }
    return codeSource.getLocation() != null;
  }
  private static String toVMName(String srcName)
  {
    return srcName.replace('.', '/');
  }
}

核心的两条代码:transform

try{
  this.classFileDumper.dump(classname, classfileBuffer);
  return this.instrumenter.instrument(classfileBuffer, classname);
}

Jaococ使用asm实现字节码植入,是对指令级别上的字节码植入,从而可以定位到执行的代码行,以达到覆盖率的统计。

flow 包分析

接着看

Instruction

/*******************************************************************************
 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.internal.flow;
import org.objectweb.asm.tree.AbstractInsnNode;
import java.util.BitSet;
/**
 * Representation of a byte code instruction for analysis. Internally used for
 * analysis.
 */
public class Instruction {
  private final AbstractInsnNode node;
  private final int line;
  private int branches;
  private final BitSet coveredBranches;
  private Instruction predecessor;
  private int predecessorBranch;
  /**
   * New instruction at the given line.
   * 
   * @param node
   *            corresponding node
   * @param line
   *            source line this instruction belongs to
   */
  public Instruction(final AbstractInsnNode node, final int line) {
    this.node = node;
    this.line = line;
    this.branches = 0;
    this.coveredBranches = new BitSet();
  }
  /**
   * @return corresponding node
   */
  public AbstractInsnNode getNode() {
    return node;
  }
  /**
   * Adds an branch to this instruction.
   */
  public void addBranch() {
    branches++;
  }
  /**
   * Sets the given instruction as a predecessor of this instruction and adds
   * branch to the predecessor. Probes are inserted in a way that every
   * instruction has at most one direct predecessor.
   * 
   * @see #addBranch()
   * @param predecessor
   *            predecessor instruction
   * @param branch
   *            branch number in predecessor that should be marked as covered
   *            when this instruction marked as covered
   */
  public void setPredecessor(final Instruction predecessor,
      final int branch) {
    this.predecessor = predecessor;
    predecessor.addBranch();
    this.predecessorBranch = branch;
  }
  /**
   * Marks one branch of this instruction as covered. Also recursively marks
   * all predecessor instructions as covered if this is the first covered
   * branch.
   *
   * @param branch
   *            branch number to mark as covered
   */
  public void setCovered(final int branch) {
    Instruction i = this;
    int b = branch;
    while (i != null) {
      if (!i.coveredBranches.isEmpty()) {
        i.coveredBranches.set(b);
        break;
      }
      i.coveredBranches.set(b);
      b = i.predecessorBranch;
      i = i.predecessor;
    }
  }
  /**
   * Returns the source line this instruction belongs to.
   * 
   * @return corresponding source line
   */
  public int getLine() {
    return line;
  }
  /**
   * Returns the total number of branches starting from this instruction.
   * 
   * @return total number of branches
   */
  public int getBranches() {
    return branches;
  }
  /**
   * Returns the number of covered branches starting from this instruction.
   * 
   * @return number of covered branches
   */
  public int getCoveredBranches() {
    return coveredBranches.cardinality();
  }
  /**
   * Merges information about covered branches of given instruction into this
   * instruction.
   * 
   * @param instruction
   *            instruction from which to merge
   */
  public void merge(Instruction instruction) {
    this.coveredBranches.or(instruction.coveredBranches);
  }
  @Override
  public String toString() {
    return coveredBranches.toString();
  }
}

记录对应指令的代码行,记录在跳转的label处对应的代码行数,那么类推可以等到整个覆盖和未覆盖的代码行。


接着看看具体植入的是什么指令


org.jacoco.core.internal.instr.ProbeArrayStrategyFactory

工厂:用于寻找合适的策略来访问给定类的探针数组


createFor()

为给定 reader 描述的 class 创建合适的策略实例。 创建的实例只能用于处理为其创建了实例的类或接口,并且只能使用一次。

  /**
   * @param classId
   *            class identifier
   * @param reader
   *            reader to get information about the class
   * @param accessorGenerator
   *            accessor to the coverage runtime
   * @return strategy instance
   */
  public static IProbeArrayStrategy createFor(final long classId,
      final ClassReader reader,
      final IExecutionDataAccessorGenerator accessorGenerator) {
    final String className = reader.getClassName();
    final int version = InstrSupport.getMajorVersion(reader);
    if (isInterfaceOrModule(reader)) {
      final ProbeCounter counter = getProbeCounter(reader);
      if (counter.getCount() == 0) {
        return new NoneProbeArrayStrategy();
      }
      if (version >= Opcodes.V11 && counter.hasMethods()) {
        return new CondyProbeArrayStrategy(className, true, classId,
            accessorGenerator);
      }
      if (version >= Opcodes.V1_8 && counter.hasMethods()) {
        return new InterfaceFieldProbeArrayStrategy(className, classId,
            counter.getCount(), accessorGenerator);
      } else {
        return new LocalProbeArrayStrategy(className, classId,
            counter.getCount(), accessorGenerator);
      }
    } else {
      if (version >= Opcodes.V11) {
        return new CondyProbeArrayStrategy(className, false, classId,
            accessorGenerator);
      }
      return new ClassFieldProbeArrayStrategy(className, classId,
          InstrSupport.needsFrames(version), accessorGenerator);
    }
  }

IProbeArrayStrategy

其实现类如下:

37.png

isInterfaceOrModule

private static boolean isInterfaceOrModule(final ClassReader reader) {
    return (reader.getAccess()
        & (Opcodes.ACC_INTERFACE | Opcodes.ACC_MODULE)) != 0;
}

ClassFieldProbeArrayStrategy

常规类的策略添加了一个用于保存探针数组的 static 字段和一个从运行时请求探针数组的静态初始化方法。

属性

/**
 * Frame stack with a single boolean array.
 */
private static final Object[] FRAME_STACK_ARRZ = new Object[] {
    InstrSupport.DATAFIELD_DESC };
/**
 * Empty frame locals.
 */
private static final Object[] FRAME_LOCALS_EMPTY = new Object[0];
private final String className;
private final long classId;
private final boolean withFrames;
private final IExecutionDataAccessorGenerator accessorGenerator;
目录
相关文章
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1327 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
539 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
移动开发 前端开发 JavaScript
从入门到精通:H5游戏源码开发技术全解析与未来趋势洞察
H5游戏凭借其跨平台、易传播和开发成本低的优势,近年来发展迅猛。接下来,让我们深入了解 H5 游戏源码开发的技术教程以及未来的发展趋势。
|
存储 前端开发 JavaScript
在线教育网课系统源码开发指南:功能设计与技术实现深度解析
在线教育网课系统是近年来发展迅猛的教育形式的核心载体,具备用户管理、课程管理、教学互动、学习评估等功能。本文从功能和技术两方面解析其源码开发,涵盖前端(HTML5、CSS3、JavaScript等)、后端(Java、Python等)、流媒体及云计算技术,并强调安全性、稳定性和用户体验的重要性。
|
机器学习/深度学习 自然语言处理 算法
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
生成式 AI 大语言模型(LLMs)核心算法及源码解析:预训练篇
3895 1
|
负载均衡 JavaScript 前端开发
分片上传技术全解析:原理、优势与应用(含简单实现源码)
分片上传通过将大文件分割成多个小的片段或块,然后并行或顺序地上传这些片段,从而提高上传效率和可靠性,特别适用于大文件的上传场景,尤其是在网络环境不佳时,分片上传能有效提高上传体验。 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
514 2
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是"将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。创建型模式分为5种:单例模式、工厂方法模式抽象工厂式、原型模式、建造者模式。
1297 2
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
1567 1
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析

热门文章

最新文章

推荐镜像

更多
  • DNS