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