| /******************************************************************************* |
| * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Marc R. Hoffmann - initial API and implementation |
| * |
| *******************************************************************************/ |
| package org.jacoco.agent.rt.internal; |
| |
| import java.lang.instrument.ClassFileTransformer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.security.CodeSource; |
| import java.security.ProtectionDomain; |
| |
| import org.jacoco.core.instr.Instrumenter; |
| import org.jacoco.core.runtime.AgentOptions; |
| import org.jacoco.core.runtime.IRuntime; |
| import org.jacoco.core.runtime.WildcardMatcher; |
| |
| /** |
| * Class file transformer to instrument classes for code coverage analysis. |
| */ |
| public class CoverageTransformer implements ClassFileTransformer { |
| |
| private static final String AGENT_PREFIX; |
| |
| static { |
| final String name = CoverageTransformer.class.getName(); |
| AGENT_PREFIX = toVMName(name.substring(0, name.lastIndexOf('.'))); |
| } |
| |
| private final Instrumenter instrumenter; |
| |
| private final IExceptionLogger logger; |
| |
| private final WildcardMatcher includes; |
| |
| private final WildcardMatcher excludes; |
| |
| private final WildcardMatcher exclClassloader; |
| |
| private final ClassFileDumper classFileDumper; |
| |
| private final boolean inclBootstrapClasses; |
| |
| private final boolean inclNoLocationClasses; |
| |
| /** |
| * New transformer with the given delegates. |
| * |
| * @param runtime |
| * coverage runtime |
| * @param options |
| * configuration options for the generator |
| * @param logger |
| * logger for exceptions during instrumentation |
| */ |
| public CoverageTransformer(final IRuntime runtime, |
| final AgentOptions options, final IExceptionLogger logger) { |
| this.instrumenter = new Instrumenter(runtime); |
| this.logger = logger; |
| // Class names will be reported in VM notation: |
| includes = new WildcardMatcher(toVMName(options.getIncludes())); |
| excludes = new WildcardMatcher(toVMName(options.getExcludes())); |
| exclClassloader = new WildcardMatcher(options.getExclClassloader()); |
| classFileDumper = new ClassFileDumper(options.getClassDumpDir()); |
| inclBootstrapClasses = options.getInclBootstrapClasses(); |
| inclNoLocationClasses = options.getInclNoLocationClasses(); |
| } |
| |
| public byte[] transform(final ClassLoader loader, final String classname, |
| final Class<?> classBeingRedefined, |
| final ProtectionDomain protectionDomain, |
| final byte[] classfileBuffer) throws IllegalClassFormatException { |
| |
| // We do not support class retransformation: |
| if (classBeingRedefined != null) { |
| return null; |
| } |
| |
| if (!filter(loader, classname, protectionDomain)) { |
| return null; |
| } |
| |
| try { |
| classFileDumper.dump(classname, classfileBuffer); |
| return instrumenter.instrument(classfileBuffer, classname); |
| } catch (final Exception ex) { |
| final IllegalClassFormatException wrapper = new IllegalClassFormatException( |
| ex.getMessage()); |
| wrapper.initCause(ex); |
| // Report this, as the exception is ignored by the JVM: |
| logger.logExeption(wrapper); |
| throw wrapper; |
| } |
| } |
| |
| /** |
| * Checks whether this class should be instrumented. |
| * |
| * @param loader |
| * loader for the class |
| * @param classname |
| * VM name of the class to check |
| * @param protectionDomain |
| * protection domain for the class |
| * @return <code>true</code> if the class should be instrumented |
| */ |
| boolean filter(final ClassLoader loader, final String classname, |
| final ProtectionDomain protectionDomain) { |
| if (loader == null) { |
| if (!inclBootstrapClasses) { |
| return false; |
| } |
| } else { |
| if (!inclNoLocationClasses && !hasSourceLocation(protectionDomain)) { |
| return false; |
| } |
| if (exclClassloader.matches(loader.getClass().getName())) { |
| return false; |
| } |
| } |
| |
| return !classname.startsWith(AGENT_PREFIX) && |
| |
| includes.matches(classname) && |
| |
| !excludes.matches(classname); |
| } |
| |
| /** |
| * Checks whether this protection domain is associated with a source |
| * location. |
| * |
| * @param protectionDomain |
| * protection domain to check (or <code>null</code>) |
| * @return <code>true</code> if a source location is defined |
| */ |
| private boolean hasSourceLocation(final ProtectionDomain protectionDomain) { |
| if (protectionDomain == null) { |
| return false; |
| } |
| final CodeSource codeSource = protectionDomain.getCodeSource(); |
| if (codeSource == null) { |
| return false; |
| } |
| return codeSource.getLocation() != null; |
| } |
| |
| private static String toVMName(final String srcName) { |
| return srcName.replace('.', '/'); |
| } |
| |
| } |