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 *******************************************************************************/ 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 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 */ 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 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 */ 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 */ 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 150 private static String toVMName(final String srcName) { 151 return srcName.replace('.', '/'); 152 } 153 154} 155