前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jacoco探针源码解析(0.8.5 版本)

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

作者头像
JavaEdge
发布2020-05-27 11:20:20
2.3K0
发布2020-05-27 11:20:20
举报
文章被收录于专栏:JavaEdgeJavaEdge

0 前言

全是干货的技术殿堂

文章收录在我的 GitHub 仓库,欢迎Star/fork: Java-Interview-Tutorial https://github.com/Wasabi1234/Java-Interview-Tutorial

1 jacoco agent入口类

查看 MANIFEST.MF 文件

入口类—PreMain 通过 agent 参数和命令行配置,通过 JVM 初始化 agent 时调用,使得 javaagent 得以在 JVM 启动后,应用启动前载入,通知自己 ok 且 javaagent 可用于 instrument 和 transform 字节码了

  • 参数 Instrumentation类是java.lang 下面的类
  • 然后调用了CoverageTransformer 类,这个类和 premain同一个包下面:
代码语言:javascript
复制
/*******************************************************************************
 * Copyright (c) 2009, 2020 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.agent.rt.internal;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.CodeSource;
import java.security.ProtectionDomain;

import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.AgentOptions;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.WildcardMatcher;

/**
 * Class file transformer to instrument classes for code coverage analysis.
 */
public class CoverageTransformer implements ClassFileTransformer {

	private static final String AGENT_PREFIX;

	static {
		final String name = CoverageTransformer.class.getName();
		AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
	}

	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;

	/**
	 * New transformer with the given delegates.
	 *
	 * @param runtime
	 *            coverage runtime
	 * @param options
	 *            configuration options for the generator
	 * @param logger
	 *            logger for exceptions during instrumentation
	 */
	public CoverageTransformer(final IRuntime runtime,
			final AgentOptions options, final IExceptionLogger logger) {
		this.instrumenter = new Instrumenter(runtime);
		this.logger = logger;
		// Class names will be reported in VM notation:
		includes = new WildcardMatcher(toVMName(options.getIncludes()));
		excludes = new WildcardMatcher(toVMName(options.getExcludes()));
		exclClassloader = new WildcardMatcher(options.getExclClassloader());
		classFileDumper = new ClassFileDumper(options.getClassDumpDir());
		inclBootstrapClasses = options.getInclBootstrapClasses();
		inclNoLocationClasses = options.getInclNoLocationClasses();
	}
  	
  	// 这个类中的关键方法:
	public byte[] transform(final ClassLoader loader, final String classname,
			final Class<?> classBeingRedefined,
			final ProtectionDomain protectionDomain,
			final byte[] classfileBuffer) throws IllegalClassFormatException {

		// We do not support class retransformation:
		if (classBeingRedefined != null) {
			return null;
		}

		if (!filter(loader, classname, protectionDomain)) {
			return null;
		}

		try {
			classFileDumper.dump(classname, classfileBuffer);
			return instrumenter.instrument(classfileBuffer, classname);
		} catch (final Exception ex) {
			final IllegalClassFormatException wrapper = new IllegalClassFormatException(
					ex.getMessage());
			wrapper.initCause(ex);
			// Report this, as the exception is ignored by the JVM:
			logger.logExeption(wrapper);
			throw wrapper;
		}
	}

	/**
	 * Checks whether this class should be instrumented.
	 *
	 * @param loader
	 *            loader for the class
	 * @param classname
	 *            VM name of the class to check
	 * @param protectionDomain
	 *            protection domain for the class
	 * @return <code>true</code> if the class should be instrumented
	 */
	boolean filter(final ClassLoader loader, final String classname,
			final ProtectionDomain protectionDomain) {
		if (loader == null) {
			if (!inclBootstrapClasses) {
				return false;
			}
		} else {
			if (!inclNoLocationClasses
					&& !hasSourceLocation(protectionDomain)) {
				return false;
			}
			if (exclClassloader.matches(loader.getClass().getName())) {
				return false;
			}
		}

		return !classname.startsWith(AGENT_PREFIX) &&

				includes.matches(classname) &&

				!excludes.matches(classname);
	}

	/**
	 * Checks whether this protection domain is associated with a source
	 * location.
	 *
	 * @param protectionDomain
	 *            protection domain to check (or <code>null</code>)
	 * @return <code>true</code> if a source location is defined
	 */
	private boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
		if (protectionDomain == null) {
			return false;
		}
		final CodeSource codeSource = protectionDomain.getCodeSource();
		if (codeSource == null) {
			return false;
		}
		return codeSource.getLocation() != null;
	}

	private static String toVMName(final String srcName) {
		return srcName.replace('.', '/');
	}

}

transform 方法调用了

classFileDumper 类

代码语言:javascript
复制
ClassFileDumper.java
/*******************************************************************************
 * Copyright (c) 2009, 2020 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.agent.rt.internal;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.jacoco.core.internal.data.CRC64;

/**
 * 对class文件的内部dumper程序。
 */
class ClassFileDumper {

	private final File location;

	/**
	 * Create a new dumper for the given location.
	 *
	 * @param location
	 *            relative path to dump directory. <code>null</code> if no dumps
	 *            should be written
	 */
	ClassFileDumper(final String location) {
		if (location == null) {
			this.location = null;
		} else {
			this.location = new File(location);
		}
	}

	/**
	 * 如果指定了非空位置,则以给定名称 dump 给定二进制内容。
	 *
	 * @param name
	 *            qualified class name in VM notation
	 */
	void dump(final String name, final byte[] contents) throws IOException {
		if (location != null) {
			final File outputdir;
			final String localname;
			final int pkgpos = name.lastIndexOf('/');
			if (pkgpos != -1) {
				outputdir = new File(location, name.substring(0, pkgpos));
				localname = name.substring(pkgpos + 1);
			} else {
				outputdir = location;
				localname = name;
			}
			outputdir.mkdirs();
			final Long id = Long.valueOf(CRC64.classId(contents));
			final File file = new File(outputdir,
					String.format("%s.%016x.class", localname, id));
			final OutputStream out = new FileOutputStream(file);
			out.write(contents);
			out.close();
		}
	}

}

这个类将class out 输出,上面transform方法在调用的时候同时使用:

ClassAnalyzer调用

代码语言:javascript
复制
public class ClassAnalyzer extends ClassProbesVisitor implements IFilterContext {
    private final ClassCoverageImpl coverage;
    private final boolean[] probes;
    private final StringPool stringPool;
    private final Set<String> classAnnotations = new HashSet();

    public ClassAnalyzer(ClassCoverageImpl coverage, boolean[] probes, StringPool stringPool) {
        this.coverage = coverage;
        this.probes = probes;
        this.stringPool = stringPool;
    }

    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.coverage.setSignature(this.stringPool.get(signature));
        this.coverage.setSuperName(this.stringPool.get(superName));
        this.coverage.setInterfaces(this.stringPool.get(interfaces));
    }

    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        this.classAnnotations.add(desc);
        return super.visitAnnotation(desc, visible);
    }

    public void visitSource(String source, String debug) {
        this.coverage.setSourceFileName(this.stringPool.get(source));
    }

    public MethodProbesVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        InstrSupport.assertNotInstrumented(name, this.coverage.getName());
        return new MethodAnalyzer(this.stringPool.get(name), this.stringPool.get(desc), this.stringPool.get(signature), this.probes, Filters.ALL, this) {
            public void visitEnd() {
                super.visitEnd();
                IMethodCoverage methodCoverage = this.getCoverage();
                if (methodCoverage.getInstructionCounter().getTotalCount() > 0) {
                    ClassAnalyzer.this.coverage.addMethod(methodCoverage);
                }

            }
        };
    }

    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        InstrSupport.assertNotInstrumented(name, this.coverage.getName());
        return super.visitField(access, name, desc, signature, value);
    }

ClassInstrumenter

Adapter that instruments a class for coverage tracing. 适配器为类覆盖率跟踪。

代码语言:javascript
复制
@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类定义进行检测。

代码语言:javascript
复制
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),下面是剩余部分代码:

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
try{
  this.classFileDumper.dump(classname, classfileBuffer);
  return this.instrumenter.instrument(classfileBuffer, classname);
}

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

flow 包分析

接着看

Instruction

代码语言:javascript
复制
/*******************************************************************************
 * 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 创建合适的策略实例。 创建的实例只能用于处理为其创建了实例的类或接口,并且只能使用一次。

代码语言:javascript
复制
	/**
	 * @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

其实现类如下:

isInterfaceOrModule

代码语言:javascript
复制
private static boolean isInterfaceOrModule(final ClassReader reader) {
		return (reader.getAccess()
				& (Opcodes.ACC_INTERFACE | Opcodes.ACC_MODULE)) != 0;
}

ClassFieldProbeArrayStrategy

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

属性

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0 前言
  • 1 jacoco agent入口类
    • classFileDumper 类
      • ClassAnalyzer调用
        • ClassInstrumenter
        • instrumenter - 对类的植入锁定进行判断
        • loadclass入口类:CoverageTransformer
        • Instruction
        • org.jacoco.core.internal.instr.ProbeArrayStrategyFactory
          • createFor()
          • IProbeArrayStrategy
            • isInterfaceOrModule
              • ClassFieldProbeArrayStrategy
                • 属性
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档