ModifiedSystemClassRuntime.java

    1/*******************************************************************************
    2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
    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 final Class<?> systemClass;
   42
   43    private final String systemClassName;
   44
   45    private final String accessMethod;
   46
   47    private final String dataField;
   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 accessMethod
   55     *            name of the public static access method
   56     * @param dataField
   57     *            name of the public static data field
   58     * 
   59     */
   60    public ModifiedSystemClassRuntime(final Class<?> systemClass,
   61            final String accessMethod, final String dataField) {
   62        this.systemClass = systemClass;
   63        this.systemClassName = systemClass.getName().replace('.', '/');
   64        this.accessMethod = accessMethod;
   65        this.dataField = dataField;
   66    }
   67
   68    public void startup() throws Exception {
   69        final Field field = systemClass.getField(dataField);
   70        field.set(null, new MapAdapter(store));
   71    }
   72
   73    public void shutdown() {
   74        // nothing to do
   75    }
   76
   77    public int generateDataAccessor(final long classid, final MethodVisitor mv) {
   78
   79        mv.visitLdcInsn(Long.valueOf(classid));
   80
   81        // Stack[1]: J
   82        // Stack[0]: .
   83
   84        mv.visitMethodInsn(Opcodes.INVOKESTATIC, systemClassName, accessMethod,
   85                "(J)[Z");
   86
   87        // Stack[0]: [Z
   88
   89        return 2;
   90    }
   91
   92    /**
   93     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
   94     * the data container. Members are creates with internal default names. The
   95     * given class must not have been loaded before by the agent.
   96     * 
   97     * @param inst
   98     *            instrumentation interface
   99     * @param className
  100     *            VM name of the class to use
  101     * @return new runtime instance
  102     * 
  103     * @throws ClassNotFoundException
  104     *             id the given class can not be found
  105     */
  106    public static IRuntime createFor(final Instrumentation inst,
  107            final String className) throws ClassNotFoundException {
  108        return createFor(inst, className, "$jacocoGet", "$jacocoData");
  109    }
  110
  111    /**
  112     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
  113     * the data container. The given class must not have been loaded before by
  114     * the agent.
  115     * 
  116     * @param inst
  117     *            instrumentation interface
  118     * @param className
  119     *            VM name of the class to use
  120     * @param accessMethod
  121     *            name of the added access method
  122     * @param dataField
  123     *            name of the added data field
  124     * @return new runtime instance
  125     * 
  126     * @throws ClassNotFoundException
  127     *             id the given class can not be found
  128     */
  129    public static IRuntime createFor(final Instrumentation inst,
  130            final String className, final String accessMethod,
  131            final String dataField) throws ClassNotFoundException {
  132        final boolean[] instrumented = new boolean[] { false };
  133        final ClassFileTransformer transformer = new ClassFileTransformer() {
  134            public byte[] transform(final ClassLoader loader,
  135                    final String name, final Class<?> classBeingRedefined,
  136                    final ProtectionDomain protectionDomain, final byte[] source)
  137                    throws IllegalClassFormatException {
  138                if (name.equals(className)) {
  139                    instrumented[0] = true;
  140                    return instrument(source, accessMethod, dataField);
  141                }
  142                return null;
  143            }
  144        };
  145        inst.addTransformer(transformer);
  146        final Class<?> clazz = Class.forName(className.replace('/', '.'));
  147        inst.removeTransformer(transformer);
  148        if (!instrumented[0]) {
  149            final String msg = format("Class %s was not loaded.", className);
  150            throw new RuntimeException(msg);
  151        }
  152        return new ModifiedSystemClassRuntime(clazz, accessMethod, dataField);
  153    }
  154
  155    /**
  156     * Adds the static access method and data field to the given class
  157     * definition.
  158     * 
  159     * @param source
  160     *            class definition source
  161     * @param accessMethod
  162     *            name of the access method
  163     * @param dataField
  164     *            name of the data field
  165     * @return instrumented version with added members
  166     */
  167    public static byte[] instrument(final byte[] source,
  168            final String accessMethod, final String dataField) {
  169        final ClassReader reader = new ClassReader(source);
  170        final ClassWriter writer = new ClassWriter(reader, 0);
  171        reader.accept(new ClassAdapter(writer) {
  172
  173            private String className;
  174
  175            @Override
  176            public void visit(final int version, final int access,
  177                    final String name, final String signature,
  178                    final String superName, final String[] interfaces) {
  179                className = name;
  180                super.visit(version, access, name, signature, superName,
  181                        interfaces);
  182            }
  183
  184            @Override
  185            public void visitEnd() {
  186                createDataField(cv, dataField);
  187                createAccessMethod(cv, className, accessMethod, dataField);
  188                super.visitEnd();
  189            }
  190
  191        }, ClassReader.EXPAND_FRAMES);
  192        return writer.toByteArray();
  193    }
  194
  195    private static void createDataField(final ClassVisitor visitor,
  196            final String dataField) {
  197        visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,
  198                "Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/Long;[Z>;", null);
  199    }
  200
  201    private static void createAccessMethod(final ClassVisitor visitor,
  202            final String className, final String accessMethod,
  203            final String dataField) {
  204        final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
  205        final String desc = "(J)[Z";
  206        final MethodVisitor mv = visitor.visitMethod(access, accessMethod,
  207                desc, null, null);
  208
  209        mv.visitFieldInsn(Opcodes.GETSTATIC, className, dataField,
  210                "Ljava/util/Map;");
  211
  212        // Stack[0]: Ljava/util/Map;
  213
  214        mv.visitVarInsn(Opcodes.LLOAD, 0);
  215
  216        // Stack[2]: J
  217        // Stack[1]: .
  218        // Stack[0]: Ljava/util/Map;
  219
  220        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
  221                "(J)Ljava/lang/Long;");
  222
  223        // Stack[1]: Ljava/lang/Long;
  224        // Stack[0]: Ljava/util/Map;
  225
  226        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",
  227                "(Ljava/lang/Object;)Ljava/lang/Object;");
  228
  229        // Stack[0]: Ljava/lang/Object;
  230
  231        mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
  232
  233        // Stack[0]: [Z
  234
  235        mv.visitInsn(Opcodes.ARETURN);
  236        mv.visitMaxs(3, 2);
  237        mv.visitEnd();
  238    }
  239
  240}