ClassInstrumenter.java

    1/*******************************************************************************
    2 * Copyright (c) 2009 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.Type;
   25import org.objectweb.asm.commons.GeneratorAdapter;
   26
   27/**
   28 * Adapter that instruments a class for coverage tracing.
   29 * 
   30 * @author Marc R. Hoffmann
   31 * @version $Revision: $
   32 */
   33public class ClassInstrumenter extends BlockClassAdapter {
   34
   35    private final ClassVisitor delegate;
   36
   37    private final long id;
   38
   39    private final IRuntime runtime;
   40
   41    private Type type;
   42
   43    /**
   44     * Emits a instrumented version of this class to the given class visitor.
   45     * 
   46     * @param id
   47     *            unique identifier given to this class
   48     * @param runtime
   49     *            this runtime will be used for instrumentation
   50     * @param cv
   51     *            next delegate in the visitor chain will receive the
   52     *            instrumented class
   53     */
   54    public ClassInstrumenter(final long id, final IRuntime runtime,
   55            final ClassVisitor cv) {
   56        this.delegate = cv;
   57        this.id = id;
   58        this.runtime = runtime;
   59    }
   60
   61    public void visit(final int version, final int access, final String name,
   62            final String signature, final String superName,
   63            final String[] interfaces) {
   64        this.type = Type.getObjectType(name);
   65        delegate.visit(version, access, name, signature, superName, interfaces);
   66    }
   67
   68    public FieldVisitor visitField(final int access, final String name,
   69            final String desc, final String signature, final Object value) {
   70        assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
   71        return delegate.visitField(access, name, desc, signature, value);
   72    }
   73
   74    @Override
   75    protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
   76            final String name, final String desc, final String signature,
   77            final String[] exceptions) {
   78
   79        assertNotInstrumented(name, GeneratorConstants.INIT_METHOD.getName());
   80
   81        final MethodVisitor mv = delegate.visitMethod(access, name, desc,
   82                signature, exceptions);
   83
   84        if (mv == null) {
   85            return null;
   86        }
   87        return new MethodInstrumenter(mv, access, name, desc, type);
   88    }
   89
   90    @Override
   91    protected MethodVisitor visitAbstractMethod(final int access,
   92            final String name, final String desc, final String signature,
   93            final String[] exceptions) {
   94        return delegate.visitMethod(access, name, desc, signature, exceptions);
   95    }
   96
   97    public void visitEnd() {
   98        createDataField();
   99        createInitMethod();
  100        registerClass();
  101        delegate.visitEnd();
  102    }
  103
  104    private void createDataField() {
  105        delegate.visitField(GeneratorConstants.DATAFIELD_ACC,
  106                GeneratorConstants.DATAFIELD_NAME,
  107                GeneratorConstants.PROBEDATA_TYPE.getDescriptor(), null, null);
  108    }
  109
  110    private void createInitMethod() {
  111        final int access = GeneratorConstants.INIT_METHOD_ACC;
  112        final String name = GeneratorConstants.INIT_METHOD.getName();
  113        final String desc = GeneratorConstants.INIT_METHOD.getDescriptor();
  114        final GeneratorAdapter gen = new GeneratorAdapter(delegate.visitMethod(
  115                access, name, desc, null, null), access, name, desc);
  116
  117        // Load the value of the static data field:
  118        gen.getStatic(type, GeneratorConstants.DATAFIELD_NAME,
  119                GeneratorConstants.PROBEDATA_TYPE);
  120        gen.dup();
  121
  122        // Stack[1]: [Z
  123        // Stack[0]: [Z
  124
  125        // Skip initialization when we already have a data array:
  126        final Label alreadyInitialized = new Label();
  127        gen.ifNonNull(alreadyInitialized);
  128
  129        // Stack[0]: [Z
  130
  131        gen.pop();
  132        final int size = genInitializeDataField(gen);
  133
  134        // Stack[0]: [Z
  135
  136        // Return the method's block array:
  137        gen.visitLabel(alreadyInitialized);
  138        gen.returnValue();
  139
  140        gen.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
  141        gen.visitEnd();
  142    }
  143
  144    /**
  145     * Generates the byte code to initialize the static coverage data field
  146     * within this class.
  147     * 
  148     * The code will push the [[Z data array on the operand stack.
  149     * 
  150     * @param gen
  151     *            generator to emit code to
  152     */
  153    private int genInitializeDataField(final GeneratorAdapter gen) {
  154        final int size = runtime.generateDataAccessor(id, gen);
  155
  156        // Stack[0]: [Z
  157
  158        gen.dup();
  159
  160        // Stack[1]: [Z
  161        // Stack[0]: [Z
  162
  163        gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,
  164                GeneratorConstants.PROBEDATA_TYPE);
  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.", type.getClassName()));
  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, type.getInternalName(), 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}