CoverageTransformer.java revision bc47c020906fde9ce162597cf499fb66e73920fb
1/*******************************************************************************
2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Marc R. Hoffmann - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.agent.rt.internal;
13
14import java.lang.instrument.ClassFileTransformer;
15import java.lang.instrument.IllegalClassFormatException;
16import java.security.CodeSource;
17import java.security.ProtectionDomain;
18
19import org.jacoco.core.instr.Instrumenter;
20import org.jacoco.core.runtime.AgentOptions;
21import org.jacoco.core.runtime.IRuntime;
22import org.jacoco.core.runtime.WildcardMatcher;
23
24/**
25 * Class file transformer to instrument classes for code coverage analysis.
26 */
27public class CoverageTransformer implements ClassFileTransformer {
28
29	private static final String AGENT_PREFIX;
30
31	static {
32		final String name = CoverageTransformer.class.getName();
33		AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.')));
34	}
35
36	private final Instrumenter instrumenter;
37
38	private final IExceptionLogger logger;
39
40	private final WildcardMatcher includes;
41
42	private final WildcardMatcher excludes;
43
44	private final WildcardMatcher exclClassloader;
45
46	private final ClassFileDumper classFileDumper;
47
48	private final boolean includeBootstrapClasses;
49
50	/**
51	 * New transformer with the given delegates.
52	 *
53	 * @param runtime
54	 *            coverage runtime
55	 * @param options
56	 *            configuration options for the generator
57	 * @param logger
58	 *            logger for exceptions during instrumentation
59	 */
60	public CoverageTransformer(final IRuntime runtime,
61			final AgentOptions options, final IExceptionLogger logger) {
62		this.instrumenter = new Instrumenter(runtime);
63		this.logger = logger;
64		// Class names will be reported in VM notation:
65		includes = new WildcardMatcher(toVMName(options.getIncludes()));
66		excludes = new WildcardMatcher(toVMName(options.getExcludes()));
67		exclClassloader = new WildcardMatcher(options.getExclClassloader());
68		classFileDumper = new ClassFileDumper(options.getClassDumpDir());
69		includeBootstrapClasses = options.getInclBootstrapClasses();
70	}
71
72	public byte[] transform(final ClassLoader loader, final String classname,
73			final Class<?> classBeingRedefined,
74			final ProtectionDomain protectionDomain,
75			final byte[] classfileBuffer) throws IllegalClassFormatException {
76
77		// We do not support class retransformation:
78		if (classBeingRedefined != null) {
79			return null;
80		}
81
82		// We exclude dynamically created non-bootstrap classes.
83		if (loader != null && !hasSourceLocation(protectionDomain)) {
84			return null;
85		}
86
87		if (!filter(loader, classname)) {
88			return null;
89		}
90
91		try {
92			classFileDumper.dump(classname, classfileBuffer);
93			return instrumenter.instrument(classfileBuffer, classname);
94		} catch (final Exception ex) {
95			final IllegalClassFormatException wrapper = new IllegalClassFormatException(
96					ex.getMessage());
97			wrapper.initCause(ex);
98			// Report this, as the exception is ignored by the JVM:
99			logger.logExeption(wrapper);
100			throw wrapper;
101		}
102	}
103
104	/**
105	 * Checks whether this protection domain is associated with a source
106	 * location.
107	 *
108	 * @param protectionDomain
109	 *            protection domain to check (or <code>null</code>)
110	 * @return <code>true</code> if a source location is defined
111	 */
112	boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
113		if (protectionDomain == null) {
114			return false;
115		}
116		final CodeSource codeSource = protectionDomain.getCodeSource();
117		if (codeSource == null) {
118			return false;
119		}
120		return codeSource.getLocation() != null;
121	}
122
123	/**
124	 * Checks whether this class should be instrumented.
125	 *
126	 * @param loader
127	 *            loader for the class
128	 * @param classname
129	 *            VM name of the class to check
130	 * @return <code>true</code> if the class should be instrumented
131	 */
132	boolean filter(final ClassLoader loader, final String classname) {
133		if (loader == null) {
134			if (!includeBootstrapClasses) {
135				return false;
136			}
137		} else {
138			if (exclClassloader.matches(loader.getClass().getName())) {
139				return false;
140			}
141		}
142
143		return !classname.startsWith(AGENT_PREFIX) &&
144
145		includes.matches(classname) &&
146
147		!excludes.matches(classname);
148	}
149
150	private static String toVMName(final String srcName) {
151		return srcName.replace('.', '/');
152	}
153
154}
155