ClassInstrumenter.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.instr;
   13
   14import static java.lang.String.format;
   15
   16import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
   17import org.objectweb.asm.ClassAdapter;
   18import org.objectweb.asm.ClassVisitor;
   19import org.objectweb.asm.FieldVisitor;
   20import org.objectweb.asm.Label;
   21import org.objectweb.asm.MethodVisitor;
   22import org.objectweb.asm.Opcodes;
   23
   24/**
   25 * Adapter that instruments a class for coverage tracing.
   26 * 
   27 * @author Marc R. Hoffmann
   28 * @version 0.4.1.20101007204400
   29 */
   30class ClassInstrumenter extends ClassAdapter implements IBlockClassVisitor {
   31
   32    private static final Object[] STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
   33
   34    private final long id;
   35
   36    private final IExecutionDataAccessorGenerator accessorGenerator;
   37
   38    private IProbeArrayStrategy probeArrayStrategy;
   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 accessorGenerator
   48     *            this generator 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,
   54            final IExecutionDataAccessorGenerator accessorGenerator,
   55            final ClassVisitor cv) {
   56        super(cv);
   57        this.id = id;
   58        this.accessorGenerator = accessorGenerator;
   59    }
   60
   61    @Override
   62    public void visit(final int version, final int access, final String name,
   63            final String signature, final String superName,
   64            final String[] interfaces) {
   65        this.className = name;
   66        if ((access & Opcodes.ACC_INTERFACE) == 0) {
   67            this.probeArrayStrategy = new ClassTypeStrategy();
   68        } else {
   69            this.probeArrayStrategy = new InterfaceTypeStrategy();
   70        }
   71        super.visit(version, access, name, signature, superName, interfaces);
   72    }
   73
   74    @Override
   75    public FieldVisitor visitField(final int access, final String name,
   76            final String desc, final String signature, final Object value) {
   77        assertNotInstrumented(name, InstrSupport.DATAFIELD_NAME);
   78        return super.visitField(access, name, desc, signature, value);
   79    }
   80
   81    @Override
   82    public IBlockMethodVisitor visitMethod(final int access, final String name,
   83            final String desc, final String signature, final String[] exceptions) {
   84
   85        assertNotInstrumented(name, InstrSupport.INITMETHOD_NAME);
   86
   87        final MethodVisitor mv = super.visitMethod(access, name, desc,
   88                signature, exceptions);
   89
   90        if (mv == null) {
   91            return null;
   92        }
   93        return new MethodInstrumenter(mv, access, desc, probeArrayStrategy);
   94    }
   95
   96    public void visitTotalProbeCount(final int count) {
   97        probeArrayStrategy.addMembers(cv, count);
   98    }
   99
  100    /**
  101     * Ensures that the given member does not correspond to a internal member
  102     * created by the instrumentation process. This would mean that the class
  103     * has been instrumented twice.
  104     * 
  105     * @param member
  106     *            name of the member to check
  107     * @param instrMember
  108     *            name of a instrumentation member
  109     * @throws IllegalStateException
  110     *             thrown if the member has the same name than the
  111     *             instrumentation member
  112     */
  113    private void assertNotInstrumented(final String member,
  114            final String instrMember) throws IllegalStateException {
  115        if (member.equals(instrMember)) {
  116            throw new IllegalStateException(format(
  117                    "Class %s is already instrumented.", className));
  118        }
  119    }
  120
  121    // === probe array strategies ===
  122
  123    private class ClassTypeStrategy implements IProbeArrayStrategy {
  124
  125        public int pushInstance(final MethodVisitor mv) {
  126            mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
  127                    InstrSupport.INITMETHOD_NAME, InstrSupport.INITMETHOD_DESC);
  128            return 1;
  129        }
  130
  131        public void addMembers(final ClassVisitor delegate, final int probeCount) {
  132            createDataField();
  133            createInitMethod(probeCount);
  134        }
  135
  136        private void createDataField() {
  137            cv.visitField(InstrSupport.DATAFIELD_ACC,
  138                    InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC,
  139                    null, null);
  140        }
  141
  142        private void createInitMethod(final int probeCount) {
  143            final MethodVisitor mv = cv.visitMethod(
  144                    InstrSupport.INITMETHOD_ACC, InstrSupport.INITMETHOD_NAME,
  145                    InstrSupport.INITMETHOD_DESC, null, null);
  146
  147            // Load the value of the static data field:
  148            mv.visitFieldInsn(Opcodes.GETSTATIC, className,
  149                    InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
  150            mv.visitInsn(Opcodes.DUP);
  151
  152            // Stack[1]: [Z
  153            // Stack[0]: [Z
  154
  155            // Skip initialization when we already have a data array:
  156            final Label alreadyInitialized = new Label();
  157            mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
  158
  159            // Stack[0]: [Z
  160
  161            mv.visitInsn(Opcodes.POP);
  162            final int size = genInitializeDataField(mv, probeCount);
  163
  164            // Stack[0]: [Z
  165
  166            // Return the method's block array:
  167            mv.visitFrame(Opcodes.F_FULL, 0, new Object[0], 1, STACK_ARRZ);
  168            mv.visitLabel(alreadyInitialized);
  169            mv.visitInsn(Opcodes.ARETURN);
  170
  171            mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
  172            mv.visitEnd();
  173        }
  174
  175        /**
  176         * Generates the byte code to initialize the static coverage data field
  177         * within this class.
  178         * 
  179         * The code will push the [Z data array on the operand stack.
  180         * 
  181         * @param mv
  182         *            generator to emit code to
  183         */
  184        private int genInitializeDataField(final MethodVisitor mv,
  185                final int probeCount) {
  186            final int size = accessorGenerator.generateDataAccessor(id,
  187                    className, probeCount, mv);
  188
  189            // Stack[0]: [Z
  190
  191            mv.visitInsn(Opcodes.DUP);
  192
  193            // Stack[1]: [Z
  194            // Stack[0]: [Z
  195
  196            mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
  197                    InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC);
  198
  199            // Stack[0]: [Z
  200
  201            return Math.max(size, 2); // Maximum local stack size is 2
  202        }
  203    }
  204
  205    private class InterfaceTypeStrategy implements IProbeArrayStrategy {
  206
  207        public int pushInstance(final MethodVisitor mv) {
  208            // TODO As we don't know the actual probe count at this point in
  209            // time use a hard coded value of 64. This is typically be way too
  210            // big and will break static initializers in interfaces with more
  211            // than 64 blocks. So this has to be replaced with the actual probe
  212            // count.
  213            final int size = accessorGenerator.generateDataAccessor(id,
  214                    className, 64, mv);
  215            return size;
  216        }
  217
  218        public void addMembers(final ClassVisitor delegate, final int probeCount) {
  219        }
  220
  221    }
  222
  223}