Jacoco源码阅读(1)

jacoco到底是在哪里插桩的,如何插桩?

jacoco主要使用ASM进行字节码操作,可以看个小Demo了解一下:https://www.jianshu.com/p/905be2a9a700

GitHub上下载jacoco后,可以从example包里的代码做为入口看源码。

这是其中一个Demo:

主要看main方法里面只执行了execute()方法。

两个内部类,其中TestTarget是要进行插桩和生成报告的类。

另一个MemoryClassLoader是自己定义的ClassLoader。

/*******************************************************************************
 * 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.examples;

import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jacoco.core.analysis.Analyzer;
import org.jacoco.core.analysis.CoverageBuilder;
import org.jacoco.core.analysis.IClassCoverage;
import org.jacoco.core.analysis.ICounter;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.SessionInfoStore;
import org.jacoco.core.instr.Instrumenter;
import org.jacoco.core.runtime.IRuntime;
import org.jacoco.core.runtime.LoggerRuntime;
import org.jacoco.core.runtime.RuntimeData;

/**
 * Example usage of the JaCoCo core API. In this tutorial a single target class
 * will be instrumented and executed. Finally the coverage information will be
 * dumped.
 */
public final class CoreTutorial {

	private final static Logger LOG = Logger.getLogger(CoreTutorial.class);

	/**
	 * The test target we want to see code coverage for.
	 */
	public static class TestTarget implements Runnable {

		public void run() {
			isPrime(7);
		}

		private boolean isPrime(final int n) {
			for (int i = 2; i * i <= n; i++) {
				if ((n ^ i) == 0) {
					return false;
				}
			}
			return true;
		}

	}

	/**
	 * A class loader that loads classes from in-memory data.
	 */
	public static class MemoryClassLoader extends ClassLoader {

		private final Map<String, byte[]> definitions = new HashMap<String, byte[]>();

		/**
		 * Add a in-memory representation of a class.
		 *
		 * @param name
		 *            name of the class
		 * @param bytes
		 *            class definition
		 */
		public void addDefinition(final String name, final byte[] bytes) {
			definitions.put(name, bytes);
		}

		@Override
		protected Class<?> loadClass(final String name, final boolean resolve)
				throws ClassNotFoundException {
			final byte[] bytes = definitions.get(name);
			if (bytes != null) {
				return defineClass(name, bytes, 0, bytes.length);
			}
			return super.loadClass(name, resolve);
		}

	}

	private final PrintStream out;

	/**
	 * Creates a new example instance printing to the given stream.
	 *
	 * @param out
	 *            stream for outputs
	 */
	public CoreTutorial(final PrintStream out) {
		this.out = out;
	}

	/**
	 * Run this example.
	 *
	 * @throws Exception
	 *             in case of errors
	 */
	public void execute() throws Exception {
		final String targetName = TestTarget.class.getName();
		LOG.info("targetName ---" + targetName.toString());

		// For instrumentation and runtime we need a IRuntime instance
		// to collect execution data:
		final IRuntime runtime = new LoggerRuntime();

		// The Instrumenter creates a modified version of our test target class
		// that contains additional probes for execution data recording:
		final Instrumenter instr = new Instrumenter(runtime);
		// original是target的class文件
		InputStream original = getTargetClass(targetName);
		LOG.info("original--" + original.toString());
		// 这里的targetName只是用于异常信息,主要是用original读取class 为byte[]
		// instrumented是改造后的class original是原始的class文件
		final byte[] instrumented = instr.instrument(original, targetName);
		LOG.info("instrumented--" + instrumented.length);
		for (int i = 0; i < instrumented.length; i++) {
			LOG.info(String.format("instrumented[%d] == %x", i,
					instrumented[i]));
		}

		original.close();

		// Now we're ready to run our instrumented class and need to startup the
		// runtime first:
		final RuntimeData data = new RuntimeData();
		runtime.startup(data);

		// In this tutorial we use a special class loader to directly load the
		// instrumented class definition from a byte[] instances.
		final MemoryClassLoader memoryClassLoader = new MemoryClassLoader();
		memoryClassLoader.addDefinition(targetName, instrumented);
		final Class<?> targetClass = memoryClassLoader.loadClass(targetName);

		// Here we execute our test target class through its Runnable interface:
		final Runnable targetInstance = (Runnable) targetClass.newInstance();
		targetInstance.run();

		// At the end of test execution we collect execution data and shutdown
		// the runtime:
		//收集运行时数据
        //ec文件
		final ExecutionDataStore executionData = new ExecutionDataStore();
		//记录时间戳,开始时间和dump ec文件的时间(结束时间),用于多次ec文件的merge
        final SessionInfoStore sessionInfos = new SessionInfoStore();
		data.collect(executionData, sessionInfos, false);
		runtime.shutdown();

		// Together with the original class definition we can calculate coverage
		// information:
		// 根据original class文件计算出覆盖率信息
		final CoverageBuilder coverageBuilder = new CoverageBuilder();
		final Analyzer analyzer = new Analyzer(executionData, coverageBuilder);
		original = getTargetClass(targetName);
		analyzer.analyzeClass(original, targetName);
		original.close();

		// Let's dump some metrics and line coverage information:
		for (final IClassCoverage cc : coverageBuilder.getClasses()) {
			out.printf("Coverage of class %s%n", cc.getName());
			LOG.info("===" + cc.getInstructionCounter().toString());
			printCounter("instructions", cc.getInstructionCounter());
			printCounter("branches", cc.getBranchCounter());
			printCounter("lines", cc.getLineCounter());
			printCounter("methods", cc.getMethodCounter());
			printCounter("complexity", cc.getComplexityCounter());

			for (int i = cc.getFirstLine(); i <= cc.getLastLine(); i++) {
				out.printf("Line %s: %s%n", Integer.valueOf(i),
						getColor(cc.getLine(i).getStatus()));
			}
		}
	}

	private InputStream getTargetClass(final String name) {
		final String resource = '/' + name.replace('.', '/') + ".class";
		return getClass().getResourceAsStream(resource);
	}

	private void printCounter(final String unit, final ICounter counter) {
		final Integer missed = Integer.valueOf(counter.getMissedCount());
		final Integer total = Integer.valueOf(counter.getTotalCount());
		out.printf("%s of %s %s missed%n", missed, total, unit);
	}

	private String getColor(final int status) {
		switch (status) {
		case ICounter.NOT_COVERED:
			return "red";
		case ICounter.PARTLY_COVERED:
			return "yellow";
		case ICounter.FULLY_COVERED:
			return "green";
		}
		return "";
	}

	/**
	 * Entry point to run this examples as a Java application.
	 *
	 * @param args
	 *            list of program arguments
	 * @throws Exception
	 *             in case of errors
	 */
	public static void main(final String[] args) throws Exception {
		new CoreTutorial(System.out).execute();
	}

}

MethodInstrumenter类里面定义了主要是对IF、GOTO、SWITCH指令进行操作。

查看官方文档同样说明只在这些跳转指令里进行插桩:https://www.jacoco.org/jacoco/trunk/doc/flow.html

最后通过class文件里面的行号和指令号的对应就可以获取到对应行,生成最终的报告。

class MethodInstrumenter extends MethodProbesVisitor 

 在这里主要使用访问者模式

访问者模式:

ClassInstrumenter继承ClassProbesVistor,ClassInstrumenter类里面的MethodProbesVistor方法返回MethodInstrumenter对象,具体实现在MethodInstrumenter对象里面visitJumpInsnWithProbe方法。

主要用到Label对象里的信息,关于Label具体是什么,我也不是很清楚,可能需要再看看深入理解Java虚拟机关于CLass文件描述的那一章。这里面应该是将每一行指令封装成一个对象。

/*******************************************************************************
 * 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.core.internal.instr;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.TypePath;

/**
 * Internal utility to add probes into the control flow of a method. The code
 * for a probe simply sets a certain slot of a boolean array to true. In
 * addition the probe array has to be retrieved at the beginning of the method
 * and stored in a local variable.
 */
class ProbeInserter extends MethodVisitor implements IProbeInserter {

	private final IProbeArrayStrategy arrayStrategy;

	/**
	 * <code>true</code> if method is a class or interface initialization
	 * method.
	 */
	private final boolean clinit;

	/** Position of the inserted variable. */
	private final int variable;

	/** Maximum stack usage of the code to access the probe array. */
	private int accessorStackSize;

	/**
	 * Creates a new {@link 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
	 * 
	 */
	ProbeInserter(final int access, final String name, final String desc,
			final MethodVisitor mv, final IProbeArrayStrategy arrayStrategy) {
		super(InstrSupport.ASM_API_VERSION, mv);
		this.clinit = InstrSupport.CLINIT_NAME.equals(name);
		this.arrayStrategy = arrayStrategy;
		int pos = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
		// 解析方法参数
		for (final Type t : Type.getArgumentTypes(desc)) {
			pos += t.getSize();
		}
		variable = pos;
	}

//这个是插桩,对字节码进行操作的方法
相关指令可以查看:https://docs.oracle.com/javase/specs/jvms/se9/html/index.html
	public void insertProbe(final int id) {

		// For a probe we set the corresponding position in the boolean[] array
		// to true.
//进入到NextFrame
		mv.visitVarInsn(Opcodes.ALOAD, variable);

		// Stack[0]: [Z

		InstrSupport.push(mv, id);

		// Stack[1]: I
		// Stack[0]: [Z
//iconst_m1指令是类型安全的,
//如果可以有效地将类型int推入到输入操作数堆栈中,从而产生输出类型状//态。
		mv.visitInsn(Opcodes.ICONST_1);

		// Stack[2]: I
		// Stack[1]: I
		// Stack[0]: [Z
//一个bastore指令是类型安全的,
//如果可以有效地从输入操作数栈中取出匹配int、int和一个小数组类型的类型,就会产生输出类型状态
		mv.visitInsn(Opcodes.BASTORE);
	}

	@Override
	public void visitCode() {
		accessorStackSize = arrayStrategy.storeInstance(mv, clinit, variable);
		mv.visitCode();
	}

	@Override
	public final void visitVarInsn(final int opcode, final int var) {
		mv.visitVarInsn(opcode, map(var));
	}

	@Override
	public final void visitIincInsn(final int var, final int increment) {
		mv.visitIincInsn(map(var), increment);
	}

	@Override
	public final void visitLocalVariable(final String name, final String desc,
			final String signature, final Label start, final Label end,
			final int index) {
		mv.visitLocalVariable(name, desc, signature, start, end, map(index));
	}

	@Override
	public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef,
			final TypePath typePath, final Label[] start, final Label[] end,
			final int[] index, final String descriptor, final boolean visible) {
		final int[] newIndex = new int[index.length];
		for (int i = 0; i < newIndex.length; i++) {
			newIndex[i] = map(index[i]);
		}
		return mv.visitLocalVariableAnnotation(typeRef, typePath, start, end,
				newIndex, descriptor, visible);
	}

	@Override
	public void visitMaxs(final int maxStack, final int maxLocals) {
		// Max stack size of the probe code is 3 which can add to the
		// original stack size depending on the probe locations. The accessor
		// stack size is an absolute maximum, as the accessor code is inserted
		// at the very beginning of each method when the stack size is empty.
		final int increasedStack = Math.max(maxStack + 3, accessorStackSize);
		mv.visitMaxs(increasedStack, maxLocals + 1);
	}

	private int map(final int var) {
		if (var < variable) {
			return var;
		} else {
			return var + 1;
		}
	}

	@Override
	public final void visitFrame(final int type, final int nLocal,
			final Object[] local, final int nStack, final Object[] stack) {

		if (type != Opcodes.F_NEW) { // uncompressed frame
			throw new IllegalArgumentException(
					"ClassReader.accept() should be called with EXPAND_FRAMES flag");
		}

		final Object[] newLocal = new Object[Math.max(nLocal, variable) + 1];
		int idx = 0; // Arrays index for existing locals
		int newIdx = 0; // Array index for new locals
		int pos = 0; // Current variable position
		while (idx < nLocal || pos <= variable) {
			if (pos == variable) {
				newLocal[newIdx++] = InstrSupport.DATAFIELD_DESC;
				pos++;
			} else {
				if (idx < nLocal) {
					final Object t = local[idx++];
					newLocal[newIdx++] = t;
					pos++;
					if (t == Opcodes.LONG || t == Opcodes.DOUBLE) {
						pos++;
					}
				} else {
					// Fill unused slots with TOP
					newLocal[newIdx++] = Opcodes.TOP;
					pos++;
				}
			}
		}
		mv.visitFrame(type, newIdx, newLocal, nStack, stack);
	}

}

猜你喜欢

转载自blog.csdn.net/qq_23128065/article/details/105180507