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 * $Id: $
   12 *******************************************************************************/
   13package org.jacoco.core.instr;
   14
   15import static java.lang.String.format;
   16
   17import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
   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 static final Object[] STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
   35
   36    private final ClassVisitor delegate;
   37
   38    private final long id;
   39
   40    private final IExecutionDataAccessorGenerator accessorGenerator;
   41
   42    private IProbeArrayStrategy probeArrayStrategy;
   43
   44    private String className;
   45
   46    /**
   47     * Emits a instrumented version of this class to the given class visitor.
   48     * 
   49     * @param id
   50     *            unique identifier given to this class
   51     * @param accessorGenerator
   52     *            this generator will be used for instrumentation
   53     * @param cv
   54     *            next delegate in the visitor chain will receive the
   55     *            instrumented class
   56     */
   57    public ClassInstrumenter(final long id,
   58            final IExecutionDataAccessorGenerator accessorGenerator,
   59            final ClassVisitor cv) {
   60        this.delegate = cv;
   61        this.id = id;
   62        this.accessorGenerator = accessorGenerator;
   63    }
   64
   65    public void visit(final int version, final int access, final String name,
   66            final String signature, final String superName,
   67            final String[] interfaces) {
   68        this.className = name;
   69        if ((access & Opcodes.ACC_INTERFACE) == 0) {
   70            this.probeArrayStrategy = new ClassTypeStrategy();
   71        } else {
   72            this.probeArrayStrategy = new InterfaceTypeStrategy();
   73        }
   74        delegate.visit(version, access, name, signature, superName, interfaces);
   75    }
   76
   77    public FieldVisitor visitField(final int access, final String name,
   78            final String desc, final String signature, final Object value) {
   79        assertNotInstrumented(name, InstrSupport.DATAFIELD_NAME);
   80        return delegate.visitField(access, name, desc, signature, value);
   81    }
   82
   83    @Override
   84    protected IBlockMethodVisitor visitNonAbstractMethod(final int access,
   85            final String name, final String desc, final String signature,
   86            final String[] exceptions) {
   87
   88        assertNotInstrumented(name, InstrSupport.INITMETHOD_NAME);
   89
   90        final MethodVisitor mv = delegate.visitMethod(access, name, desc,
   91                signature, exceptions);
   92
   93        if (mv == null) {
   94            return null;
   95        }
   96        return new MethodInstrumenter(mv, access, desc, probeArrayStrategy);
   97    }
   98
   99    @Override
  100    protected MethodVisitor visitAbstractMethod(final int access,
  101            final String name, final String desc, final String signature,
  102            final String[] exceptions) {
  103        return delegate.visitMethod(access, name, desc, signature, exceptions);
  104    }
  105
  106    public void visitEnd() {
  107        probeArrayStrategy.addMembers(delegate);
  108        delegate.visitEnd();
  109    }
  110
  111    /**
  112     * Ensures that the given member does not correspond to a internal member
  113     * created by the instrumentation process. This would mean that the class
  114     * has been instrumented twice.
  115     * 
  116     * @param member
  117     *            name of the member to check
  118     * @param instrMember
  119     *            name of a instrumentation member
  120     * @throws IllegalStateException
  121     *             thrown if the member has the same name than the
  122     *             instrumentation member
  123     */
  124    private void assertNotInstrumented(final String member,
  125            final String instrMember) throws IllegalStateException {
  126        if (member.equals(instrMember)) {
  127            throw new IllegalStateException(format(
  128                    "Class %s is already instrumented.", className));
  129        }
  130    }
  131
  132    // Methods we simply delegate:
  133
  134    public AnnotationVisitor visitAnnotation(final String desc,
  135            final boolean visible) {
  136        return delegate.visitAnnotation(desc, visible);
  137    }
  138
  139    public void visitAttribute(final Attribute attr) {
  140        delegate.visitAttribute(attr);
  141    }
  142
  143    public void visitInnerClass(final String name, final String outerName,
  144            final String innerName, final int access) {
  145        delegate.visitInnerClass(name, outerName, innerName, access);
  146    }
  147
  148    public void visitOuterClass(final String owner, final String name,
  149            final String desc) {
  150        delegate.visitOuterClass(owner, name, desc);
  151    }
  152
  153    public void visitSource(final String source, final String debug) {
  154        delegate.visitSource(source, debug);
  155    }
  156
  157    // === probe array strategies ===
  158
  159    private class ClassTypeStrategy implements IProbeArrayStrategy {
  160
  161        public int pushInstance(final MethodVisitor mv) {
  162            mv.visitMethodInsn(Opcodes.INVOKESTATIC, className,
  163                    InstrSupport.INITMETHOD_NAME,
  164                    InstrSupport.INITMETHOD_DESC);
  165            return 1;
  166        }
  167
  168        public void addMembers(final ClassVisitor delegate) {
  169            createDataField();
  170            createInitMethod();
  171        }
  172
  173        private void createDataField() {
  174            delegate.visitField(InstrSupport.DATAFIELD_ACC,
  175                    InstrSupport.DATAFIELD_NAME,
  176                    InstrSupport.DATAFIELD_DESC, null, null);
  177        }
  178
  179        private void createInitMethod() {
  180            final MethodVisitor mv = delegate.visitMethod(
  181                    InstrSupport.INITMETHOD_ACC,
  182                    InstrSupport.INITMETHOD_NAME,
  183                    InstrSupport.INITMETHOD_DESC, null, null);
  184
  185            // Load the value of the static data field:
  186            mv.visitFieldInsn(Opcodes.GETSTATIC, className,
  187                    InstrSupport.DATAFIELD_NAME,
  188                    InstrSupport.DATAFIELD_DESC);
  189            mv.visitInsn(Opcodes.DUP);
  190
  191            // Stack[1]: [Z
  192            // Stack[0]: [Z
  193
  194            // Skip initialization when we already have a data array:
  195            final Label alreadyInitialized = new Label();
  196            mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);
  197
  198            // Stack[0]: [Z
  199
  200            mv.visitInsn(Opcodes.POP);
  201            final int size = genInitializeDataField(mv);
  202
  203            // Stack[0]: [Z
  204
  205            // Return the method's block array:
  206            mv.visitFrame(Opcodes.F_FULL, 0, new Object[0], 1, STACK_ARRZ);
  207            mv.visitLabel(alreadyInitialized);
  208            mv.visitInsn(Opcodes.ARETURN);
  209
  210            mv.visitMaxs(Math.max(size, 2), 1); // Maximum local stack size is 2
  211            mv.visitEnd();
  212        }
  213
  214        /**
  215         * Generates the byte code to initialize the static coverage data field
  216         * within this class.
  217         * 
  218         * The code will push the [Z data array on the operand stack.
  219         * 
  220         * @param mv
  221         *            generator to emit code to
  222         */
  223        private int genInitializeDataField(final MethodVisitor mv) {
  224            final int size = accessorGenerator.generateDataAccessor(id,
  225                    className, getProbeCount(), mv);
  226
  227            // Stack[0]: [Z
  228
  229            mv.visitInsn(Opcodes.DUP);
  230
  231            // Stack[1]: [Z
  232            // Stack[0]: [Z
  233
  234            mv.visitFieldInsn(Opcodes.PUTSTATIC, className,
  235                    InstrSupport.DATAFIELD_NAME,
  236                    InstrSupport.DATAFIELD_DESC);
  237
  238            // Stack[0]: [Z
  239
  240            return Math.max(size, 2); // Maximum local stack size is 2
  241        }
  242    }
  243
  244    private class InterfaceTypeStrategy implements IProbeArrayStrategy {
  245
  246        public int pushInstance(final MethodVisitor mv) {
  247            // TODO As we don't know the actual probe count at this point in
  248            // time use a hard coded value of 64. This is typically be way too
  249            // big and will break static initializers in interfaces with more
  250            // than 64 blocks. So this has to be replaced with the actual probe
  251            // count.
  252            final int size = accessorGenerator.generateDataAccessor(id,
  253                    className, 64, mv);
  254            return size;
  255        }
  256
  257        public void addMembers(final ClassVisitor delegate) {
  258        }
  259    }
  260
  261}