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