• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *******************************************************************************/
12 package org.jacoco.agent.rt.internal;
13 
14 import java.lang.instrument.ClassFileTransformer;
15 import java.lang.instrument.IllegalClassFormatException;
16 import java.security.CodeSource;
17 import java.security.ProtectionDomain;
18 
19 import org.jacoco.core.instr.Instrumenter;
20 import org.jacoco.core.runtime.AgentOptions;
21 import org.jacoco.core.runtime.IRuntime;
22 import org.jacoco.core.runtime.WildcardMatcher;
23 
24 /**
25  * Class file transformer to instrument classes for code coverage analysis.
26  */
27 public 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 	 */
CoverageTransformer(final IRuntime runtime, final AgentOptions options, final IExceptionLogger logger)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 
transform(final ClassLoader loader, final String classname, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer)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 	 */
hasSourceLocation(final ProtectionDomain protectionDomain)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 	 */
filter(final ClassLoader loader, final String classname)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 
toVMName(final String srcName)150 	private static String toVMName(final String srcName) {
151 		return srcName.replace('.', '/');
152 	}
153 
154 }
155