ModifiedSystemClassRuntime.java

    1/*******************************************************************************
    2 * Copyright (c) 2009, 2010 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 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.core.runtime;
   14
   15import static java.lang.String.format;
   16
   17import java.lang.instrument.ClassFileTransformer;
   18import java.lang.instrument.IllegalClassFormatException;
   19import java.lang.instrument.Instrumentation;
   20import java.lang.reflect.Field;
   21import java.security.ProtectionDomain;
   22
   23import org.objectweb.asm.ClassAdapter;
   24import org.objectweb.asm.ClassReader;
   25import org.objectweb.asm.ClassVisitor;
   26import org.objectweb.asm.ClassWriter;
   27import org.objectweb.asm.MethodVisitor;
   28import org.objectweb.asm.Opcodes;
   29
   30/**
   31 * This {@link IRuntime} implementation works with a modified system class. A
   32 * new static method is added to a bootstrap class that will be used by
   33 * instrumented classes. As the system class itself needs to be instrumented
   34 * this runtime requires a Java agent.
   35 * 
   36 * @author Marc R. Hoffmann
   37 * @version $Revision: $
   38 */
   39public class ModifiedSystemClassRuntime extends AbstractRuntime {
   40
   41    private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";
   42
   43    private final Class<?> systemClass;
   44
   45    private final String systemClassName;
   46
   47    private final String accessFieldName;
   48
   49    /**
   50     * Creates a new runtime based on the given class and members.
   51     * 
   52     * @param systemClass
   53     *            system class that contains the execution data
   54     * @param accessFieldName
   55     *            name of the public static runtime access field
   56     * 
   57     */
   58    public ModifiedSystemClassRuntime(final Class<?> systemClass,
   59            final String accessFieldName) {
   60        this.systemClass = systemClass;
   61        this.systemClassName = systemClass.getName().replace('.', '/');
   62        this.accessFieldName = accessFieldName;
   63    }
   64
   65    public void startup() throws Exception {
   66        final Field field = systemClass.getField(accessFieldName);
   67        field.set(null, new ExecutionDataAccess(store));
   68    }
   69
   70    public void shutdown() {
   71        // nothing to do
   72    }
   73
   74    public int generateDataAccessor(final long classid, final String classname,
   75            final int probecount, final MethodVisitor mv) {
   76
   77        mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName,
   78                ACCESS_FIELD_TYPE);
   79
   80        ExecutionDataAccess.generateAccessCall(classid, classname, probecount, mv);
   81
   82        return 6;
   83    }
   84
   85    /**
   86     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
   87     * the data container. Members are creates with internal default names. The
   88     * given class must not have been loaded before by the agent.
   89     * 
   90     * @param inst
   91     *            instrumentation interface
   92     * @param className
   93     *            VM name of the class to use
   94     * @return new runtime instance
   95     * 
   96     * @throws ClassNotFoundException
   97     *             id the given class can not be found
   98     */
   99    public static IRuntime createFor(final Instrumentation inst,
  100            final String className) throws ClassNotFoundException {
  101        return createFor(inst, className, "$jacocoAccess");
  102    }
  103
  104    /**
  105     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
  106     * the data container. The given class must not have been loaded before by
  107     * the agent.
  108     * 
  109     * @param inst
  110     *            instrumentation interface
  111     * @param className
  112     *            VM name of the class to use
  113     * @param accessFieldName
  114     *            name of the added runtime access field
  115     * @return new runtime instance
  116     * 
  117     * @throws ClassNotFoundException
  118     *             id the given class can not be found
  119     */
  120    public static IRuntime createFor(final Instrumentation inst,
  121            final String className, final String accessFieldName)
  122            throws ClassNotFoundException {
  123        final boolean[] instrumented = new boolean[] { false };
  124        final ClassFileTransformer transformer = new ClassFileTransformer() {
  125            public byte[] transform(final ClassLoader loader,
  126                    final String name, final Class<?> classBeingRedefined,
  127                    final ProtectionDomain protectionDomain, final byte[] source)
  128                    throws IllegalClassFormatException {
  129                if (name.equals(className)) {
  130                    instrumented[0] = true;
  131                    return instrument(source, accessFieldName);
  132                }
  133                return null;
  134            }
  135        };
  136        inst.addTransformer(transformer);
  137        final Class<?> clazz = Class.forName(className.replace('/', '.'));
  138        inst.removeTransformer(transformer);
  139        if (!instrumented[0]) {
  140            final String msg = format("Class %s was not loaded.", className);
  141            throw new RuntimeException(msg);
  142        }
  143        return new ModifiedSystemClassRuntime(clazz, accessFieldName);
  144    }
  145
  146    /**
  147     * Adds the static access method and data field to the given class
  148     * definition.
  149     * 
  150     * @param source
  151     *            class definition source
  152     * @param accessFieldName
  153     *            name of the runtime access field
  154     * @return instrumented version with added members
  155     */
  156    public static byte[] instrument(final byte[] source,
  157            final String accessFieldName) {
  158        final ClassReader reader = new ClassReader(source);
  159        final ClassWriter writer = new ClassWriter(reader, 0);
  160        reader.accept(new ClassAdapter(writer) {
  161
  162            @Override
  163            public void visitEnd() {
  164                createDataField(cv, accessFieldName);
  165                super.visitEnd();
  166            }
  167
  168        }, ClassReader.EXPAND_FRAMES);
  169        return writer.toByteArray();
  170    }
  171
  172    private static void createDataField(final ClassVisitor visitor,
  173            final String dataField) {
  174        visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,
  175                ACCESS_FIELD_TYPE, null, null);
  176    }
  177
  178}