1/*******************************************************************************
2 * Copyright (c) 2009, 2018 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 inclBootstrapClasses;
49
50	private final boolean inclNoLocationClasses;
51
52	/**
53	 * New transformer with the given delegates.
54	 *
55	 * @param runtime
56	 *            coverage runtime
57	 * @param options
58	 *            configuration options for the generator
59	 * @param logger
60	 *            logger for exceptions during instrumentation
61	 */
62	public CoverageTransformer(final IRuntime runtime,
63			final AgentOptions options, final IExceptionLogger logger) {
64		this.instrumenter = new Instrumenter(runtime);
65		this.logger = logger;
66		// Class names will be reported in VM notation:
67		includes = new WildcardMatcher(toVMName(options.getIncludes()));
68		excludes = new WildcardMatcher(toVMName(options.getExcludes()));
69		exclClassloader = new WildcardMatcher(options.getExclClassloader());
70		classFileDumper = new ClassFileDumper(options.getClassDumpDir());
71		inclBootstrapClasses = options.getInclBootstrapClasses();
72		inclNoLocationClasses = options.getInclNoLocationClasses();
73	}
74
75	public byte[] transform(final ClassLoader loader, final String classname,
76			final Class<?> classBeingRedefined,
77			final ProtectionDomain protectionDomain,
78			final byte[] classfileBuffer) throws IllegalClassFormatException {
79
80		// We do not support class retransformation:
81		if (classBeingRedefined != null) {
82			return null;
83		}
84
85		if (!filter(loader, classname, protectionDomain)) {
86			return null;
87		}
88
89		try {
90			classFileDumper.dump(classname, classfileBuffer);
91			return instrumenter.instrument(classfileBuffer, classname);
92		} catch (final Exception ex) {
93			final IllegalClassFormatException wrapper = new IllegalClassFormatException(
94					ex.getMessage());
95			wrapper.initCause(ex);
96			// Report this, as the exception is ignored by the JVM:
97			logger.logExeption(wrapper);
98			throw wrapper;
99		}
100	}
101
102	/**
103	 * Checks whether this class should be instrumented.
104	 *
105	 * @param loader
106	 *            loader for the class
107	 * @param classname
108	 *            VM name of the class to check
109	 * @param protectionDomain
110	 *            protection domain for the class
111	 * @return <code>true</code> if the class should be instrumented
112	 */
113	boolean filter(final ClassLoader loader, final String classname,
114			final ProtectionDomain protectionDomain) {
115		if (loader == null) {
116			if (!inclBootstrapClasses) {
117				return false;
118			}
119		} else {
120			if (!inclNoLocationClasses && !hasSourceLocation(protectionDomain)) {
121				return false;
122			}
123			if (exclClassloader.matches(loader.getClass().getName())) {
124				return false;
125			}
126		}
127
128		return !classname.startsWith(AGENT_PREFIX) &&
129
130		includes.matches(classname) &&
131
132		!excludes.matches(classname);
133	}
134
135	/**
136	 * Checks whether this protection domain is associated with a source
137	 * location.
138	 *
139	 * @param protectionDomain
140	 *            protection domain to check (or <code>null</code>)
141	 * @return <code>true</code> if a source location is defined
142	 */
143	private boolean hasSourceLocation(final ProtectionDomain protectionDomain) {
144		if (protectionDomain == null) {
145			return false;
146		}
147		final CodeSource codeSource = protectionDomain.getCodeSource();
148		if (codeSource == null) {
149			return false;
150		}
151		return codeSource.getLocation() != null;
152	}
153
154	private static String toVMName(final String srcName) {
155		return srcName.replace('.', '/');
156	}
157
158}
159