• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright (c) 2009, 2019 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 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 	 */
CoverageTransformer(final IRuntime runtime, final AgentOptions options, final IExceptionLogger logger)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 
transform(final ClassLoader loader, final String classname, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer)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 	 */
filter(final ClassLoader loader, final String classname, final ProtectionDomain protectionDomain)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 	 */
hasSourceLocation(final ProtectionDomain protectionDomain)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 
toVMName(final String srcName)154 	private static String toVMName(final String srcName) {
155 		return srcName.replace('.', '/');
156 	}
157 
158 }
159