对类的植入锁定进行判断

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

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;
	}
  • 执行流程图:
    Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java

所以最终的出口在于最下面的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;
	}

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

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

	@Override
	public void visitTotalProbeCount(final int count) {
		probeArrayStrategy.addMembers(cv, count);
	}

}

DuplicateFrameEliminator

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

ProbeInserter - 探针植入类

Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_02
内部实用程序,用于将探针添加到方法的控制流中。

探针的代码只是将布尔数组的某个插槽设置为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
	 */

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

visitmax

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

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

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

Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_04
Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_05

visitIincInsn - 访问 IINC 指令

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

visitLocalVariable - 访问局部变量声明

Visits a local variable declaration.
Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_07

    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 ASM
			setMajorVersion(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 过程创建的内部成员不对应。 这意味着该类已经被检测。
Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_08

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)的实现Java代码覆盖率框架JaCoCo的core-instr core.internal.instr 包类源码解析_Java_09

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

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