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