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