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 java.util.ArrayList;
   18import java.util.List;
   19
   20import org.jacoco.core.runtime.IRuntime;
   21import org.objectweb.asm.ClassAdapter;
   22import org.objectweb.asm.ClassVisitor;
   23import org.objectweb.asm.FieldVisitor;
   24import org.objectweb.asm.Label;
   25import org.objectweb.asm.MethodVisitor;
   26import org.objectweb.asm.Opcodes;
   27import org.objectweb.asm.Type;
   28import org.objectweb.asm.commons.EmptyVisitor;
   29import org.objectweb.asm.commons.GeneratorAdapter;
   30
   31/**
   32 * Adapter that instruments a class for coverage tracing.
   33 * 
   34 * @author Marc R. Hoffmann
   35 * @version $Revision: $
   36 */
   37public class ClassInstrumenter extends ClassAdapter {
   38
   39    private static class EmptyBlockMethodVisitor extends EmptyVisitor implements
   40            IBlockMethodVisitor {
   41
   42        public void visitBlockEndBeforeJump(final int id) {
   43        }
   44
   45        public void visitBlockEnd(final int id) {
   46        }
   47
   48    }
   49
   50    private final long id;
   51
   52    private final IRuntime runtime;
   53
   54    private final List<BlockMethodAdapter> blockCounters;
   55
   56    private Type type;
   57
   58    /**
   59     * Emits a instrumented version of this class to the given class visitor
   60     * 
   61     * @param id
   62     *            unique identifier given to this class
   63     * @param runtime
   64     *            this runtime will be used for instrumentation
   65     * @param cv
   66     *            next delegate in the visitor chain will receive the
   67     *            instrumented class
   68     */
   69    public ClassInstrumenter(final long id, final IRuntime runtime,
   70            final ClassVisitor cv) {
   71        super(cv);
   72        this.id = id;
   73        this.runtime = runtime;
   74        this.blockCounters = new ArrayList<BlockMethodAdapter>();
   75    }
   76
   77    @Override
   78    public void visit(final int version, final int access, final String name,
   79            final String signature, final String superName,
   80            final String[] interfaces) {
   81        this.type = Type.getObjectType(name);
   82        super.visit(version, access, name, signature, superName, interfaces);
   83    }
   84
   85    @Override
   86    public FieldVisitor visitField(final int access, final String name,
   87            final String desc, final String signature, final Object value) {
   88        assertNotInstrumented(name, GeneratorConstants.DATAFIELD_NAME);
   89        return super.visitField(access, name, desc, signature, value);
   90    }
   91
   92    @Override
   93    public MethodVisitor visitMethod(final int access, final String name,
   94            final String desc, final String signature, final String[] exceptions) {
   95
   96        assertNotInstrumented(name, GeneratorConstants.INIT_METHOD.getName());
   97
   98        final MethodVisitor mv = super.visitMethod(access, name, desc,
   99                signature, exceptions);
  100
  101        // Abstract methods do not have code to analyze
  102        if ((access & Opcodes.ACC_ABSTRACT) != 0) {
  103            return mv;
  104        }
  105
  106        final int methodId = blockCounters.size();
  107
  108        final IBlockMethodVisitor blockVisitor;
  109        if (mv == null) {
  110            blockVisitor = new EmptyBlockMethodVisitor();
  111        } else {
  112            blockVisitor = new MethodInstrumenter(mv, access, name, desc,
  113                    methodId, type);
  114        }
  115
  116        final BlockMethodAdapter adapter = new BlockMethodAdapter(blockVisitor,
  117                access, name, desc, signature, exceptions);
  118        blockCounters.add(adapter);
  119        return adapter;
  120    }
  121
  122    @Override
  123    public void visitEnd() {
  124        createDataField();
  125        createInitMethod();
  126        registerClass();
  127        super.visitEnd();
  128    }
  129
  130    private void createDataField() {
  131        super.visitField(GeneratorConstants.DATAFIELD_ACC,
  132                GeneratorConstants.DATAFIELD_NAME,
  133                GeneratorConstants.DATAFIELD_TYPE.getDescriptor(), null, null);
  134    }
  135
  136    private void createInitMethod() {
  137        final int access = GeneratorConstants.INIT_METHOD_ACC;
  138        final String name = GeneratorConstants.INIT_METHOD.getName();
  139        final String desc = GeneratorConstants.INIT_METHOD.getDescriptor();
  140        final GeneratorAdapter gen = new GeneratorAdapter(super.visitMethod(
  141                access, name, desc, null, null), access, name, desc);
  142
  143        // Load the value of the static data field:
  144        gen.getStatic(type, GeneratorConstants.DATAFIELD_NAME,
  145                GeneratorConstants.DATAFIELD_TYPE);
  146        gen.dup();
  147
  148        // Stack[1]: [[Z
  149        // Stack[0]: [[Z
  150
  151        // Skip initialization when we already have a data array:
  152        final Label alreadyInitialized = new Label();
  153        gen.ifNonNull(alreadyInitialized);
  154
  155        // Stack[0]: [[Z
  156
  157        gen.pop();
  158        final int size = genInitializeDataField(gen);
  159
  160        // Stack[0]: [[Z
  161
  162        // Return the method's block array:
  163        gen.visitLabel(alreadyInitialized);
  164        gen.loadArg(0);
  165
  166        // Stack[1]: I
  167        // Stack[0]: [[Z
  168
  169        gen.arrayLoad(GeneratorConstants.BLOCK_ARR);
  170
  171        // Stack[0]: [Z
  172
  173        gen.returnValue();
  174
  175        gen.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
  176        gen.visitEnd();
  177    }
  178
  179    /**
  180     * Generates the byte code to initialize the static coverage data field
  181     * within this class.
  182     * 
  183     * The code will push the [[Z data array on the operand stack.
  184     * 
  185     * @param gen
  186     *            generator to emit code to
  187     */
  188    private int genInitializeDataField(final GeneratorAdapter gen) {
  189        final int size = runtime.generateDataAccessor(id, gen);
  190
  191        // Stack[0]: [[Z
  192
  193        gen.dup();
  194
  195        // Stack[1]: [[Z
  196        // Stack[0]: [[Z
  197
  198        gen.putStatic(type, GeneratorConstants.DATAFIELD_NAME,
  199                GeneratorConstants.DATAFIELD_TYPE);
  200
  201        // Stack[0]: [[Z
  202
  203        return Math.max(size, 2); // Maximum local stack size is 2
  204    }
  205
  206    /**
  207     * Ensures that the given member does not correspond to a internal member
  208     * created by the instrumentation process. This would mean that the class
  209     * has been instrumented twice.
  210     * 
  211     * @param member
  212     *            name of the member to check
  213     * @param instrMember
  214     *            name of a instrumentation member
  215     * @throws IllegalStateException
  216     *             thrown if the member has the same name than the
  217     *             instrumentation member
  218     */
  219    private void assertNotInstrumented(final String member,
  220            final String instrMember) throws IllegalStateException {
  221        if (member.equals(instrMember)) {
  222            throw new IllegalStateException(format(
  223                    "Class %s is already instrumented.", type.getClassName()));
  224        }
  225    }
  226
  227    /**
  228     * Create a execution data structure according to the structure of this
  229     * class and registers it with the runtime.
  230     */
  231    private void registerClass() {
  232        final boolean[][] data = new boolean[blockCounters.size()][];
  233        for (int blockIdx = 0; blockIdx < blockCounters.size(); blockIdx++) {
  234            data[blockIdx] = new boolean[blockCounters.get(blockIdx)
  235                    .getBlockCount()];
  236        }
  237        runtime.registerClass(id, type.getInternalName(), data);
  238    }
  239
  240}