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