ClassInstrumenter.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.instr;
   14
   15import static java.lang.String.format;
   16
   17import org.jacoco.core.runtime.IRuntime;
   18import org.objectweb.asm.AnnotationVisitor;
   19import org.objectweb.asm.Attribute;
   20import org.objectweb.asm.ClassVisitor;
   21import org.objectweb.asm.FieldVisitor;
   22import org.objectweb.asm.Label;
   23import org.objectweb.asm.MethodVisitor;
   24import org.objectweb.asm.Opcodes;
   25
   26/**
   27 * Adapter that instruments a class for coverage tracing.
   28 * 
   29 * @author Marc R. Hoffmann
   30 * @version $Revision: $
   31 */
   32public class ClassInstrumenter extends BlockClassAdapter {
   33
   34    private final ClassVisitor delegate;
   35
   36    private final long id;
   37
   38    private final IRuntime runtime;
   39
   40    private String className;
   41
   42    /**
   43     * Emits a instrumented version of this class to the given class visitor.
   44     * 
   45     * @param id
   46     *            unique identifier given to this class
   47     * @param runtime
   48     *            this runtime will be used for instrumentation
   49     * @param cv
   50     *            next delegate in the visitor chain will receive the
   51     *            instrumented class
   52     */
   53    public ClassInstrumenter(final long id, final IRuntime runtime,
   54            final ClassVisitor cv) {
   55        this.delegate = cv;
   56        this.id = id;
   57        this.runtime = runtime;
   58    }
   59
   60    public void visit(final int version, final int access, final String name,
   61            final String signature, final String superName,
   62            final String[] interfaces) {
   63        this.className = name;
   64        delegate.visit(version, access, name, signature, superName, interfaces);
   65    }
   66
   67    public FieldVisitor visitField(final int access, final String name,
   68            final String desc, final String signature, final Object value) {
   69        assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
   70        return delegate.visitField(access, name, desc, signature, value);
   71    }
   72
   73    @Override
   74    protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
   75            final String name, final String desc, final String signature,
   76            final String[] exceptions) {
   77
   78        assertNotInstrumented(name, GeneratorConstants.INITMETHOD_NAME);
   79
   80        final MethodVisitor mv = delegate.visitMethod(access, name, desc,
   81                signature, exceptions);
   82
   83        if (mv == null) {
   84            return null;
   85        }
   86        return new MethodInstrumenter(mv, access, name, desc, className);
   87    }
   88
   89    @Override
   90    protected MethodVisitor visitAbstractMethod(final int access,
   91            final String name, final String desc, final String signature,
   92            final String[] exceptions) {
   93        return delegate.visitMethod(access, name, desc, signature, exceptions);
   94    }
   95
   96    public void visitEnd() {
   97        createDataField();
   98        createInitMethod();
   99        registerClass();
  100        delegate.visitEnd();
  101    }
  102
  103    private void createDataField() {
  104        delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
  105                GeneratorConstants.DATAFIELD_NAME,
  106                GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);
  107    }
  108
  109    private void createInitMethod() {
  110        final int access = GeneratorConstants.INITMETHOD_ACC;
  111        final MethodVisitor mv = delegate.visitMethod(access,
  112                GeneratorConstants.INITMETHOD_NAME,
  113                GeneratorConstants.INITMETHOD_DESC, null, null);
  114
  115        // Load the value of the static data field:
  116        mv.visitFieldInsn(Opcodes.GETSTATIC, className,
  117                GeneratorConstants.DATAFIELD_NAME,
  118                GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
  119        mv.visitInsn(Opcodes.DUP);
  120
  121        // Stack[1]: [Z
  122        // Stack[0]: [Z
  123
  124        // Skip initialization when we already have a data array:
  125        final Label alreadyInitialized = new Label();
  126        mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
  127
  128        // Stack[0]: [Z
  129
  130        mv.visitInsn(Opcodes.POP);
  131        final int size = genInitializeDataField(mv);
  132
  133        // Stack[0]: [Z
  134
  135        // Return the method's block array:
  136        mv.visitLabel(alreadyInitialized);
  137        mv.visitInsn(Opcodes.ARETURN);
  138
  139        mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
  140        mv.visitEnd();
  141    }
  142
  143    /**
  144     * Generates the byte code to initialize the static coverage data field
  145     * within this class.
  146     * 
  147     * The code will push the [[Z data array on the operand stack.
  148     * 
  149     * @param mv
  150     *            generator to emit code to
  151     */
  152    private int genInitializeDataField(final MethodVisitor mv) {
  153        final int size = runtime.generateDataAccessor(id, mv);
  154
  155        // Stack[0]: [Z
  156
  157        mv.visitInsn(Opcodes.DUP);
  158
  159        // Stack[1]: [Z
  160        // Stack[0]: [Z
  161
  162        mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
  163                GeneratorConstants.DATAFIELD_NAME,
  164                GeneratorConstants.PROBEDATA_TYPE.getDescriptor());
  165
  166        // Stack[0]: [Z
  167
  168        return Math.max(size, 2); // Maximum local stack size is 2
  169    }
  170
  171    /**
  172     * Ensures that the given member does not correspond to a internal member
  173     * created by the instrumentation process. This would mean that the class
  174     * has been instrumented twice.
  175     * 
  176     * @param member
  177     *            name of the member to check
  178     * @param instrMember
  179     *            name of a instrumentation member
  180     * @throws IllegalStateException
  181     *             thrown if the member has the same name than the
  182     *             instrumentation member
  183     */
  184    private void assertNotInstrumented(final String member,
  185            final String instrMember) throws IllegalStateException {
  186        if (member.equals(instrMember)) {
  187            throw new IllegalStateException(format(
  188                    "Class %s is already instrumented.", className));
  189        }
  190    }
  191
  192    /**
  193     * Create a execution data structure according to the structure of this
  194     * class and registers it with the runtime.
  195     */
  196    private void registerClass() {
  197        final boolean[] data = new boolean[getProbeCount()];
  198        runtime.registerClass(id, className, data);
  199    }
  200
  201    // Methods we simply delegate:
  202
  203    public AnnotationVisitor visitAnnotation(final String desc,
  204            final boolean visible) {
  205        return delegate.visitAnnotation(desc, visible);
  206    }
  207
  208    public void visitAttribute(final Attribute attr) {
  209        delegate.visitAttribute(attr);
  210    }
  211
  212    public void visitInnerClass(final String name, final String outerName,
  213            final String innerName, final int access) {
  214        delegate.visitInnerClass(name, outerName, innerName, access);
  215    }
  216
  217    public void visitOuterClass(final String owner, final String name,
  218            final String desc) {
  219        delegate.visitOuterClass(owner, name, desc);
  220    }
  221
  222    public void visitSource(final String source, final String debug) {
  223        delegate.visitSource(source, debug);
  224    }
  225
  226}