org.jacoco.core.internal.instr.ClassInstrumenter.java Source code

Java tutorial

Introduction

Here is the source code for org.jacoco.core.internal.instr.ClassInstrumenter.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 2013 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.internal.instr;

import org.jacoco.core.internal.flow.ClassProbesVisitor;
import org.jacoco.core.internal.flow.MethodProbesVisitor;
import org.jacoco.core.runtime.IExecutionDataAccessorGenerator;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Adapter that instruments a class for coverage tracing.
 */
public class ClassInstrumenter extends ClassProbesVisitor {

    private static final Object[] STACK_ARRZ = new Object[] { InstrSupport.DATAFIELD_DESC };
    private static final Object[] NO_LOCALS = new Object[0];

    private final long id;

    private final IExecutionDataAccessorGenerator accessorGenerator;

    private IProbeArrayStrategy probeArrayStrategy;

    private String className;

    private boolean withFrames;

    private int probeCount;

    /**
     * Emits a instrumented version of this class to the given class visitor.
     * 
     * @param id
     *            unique identifier given to this class
     * @param accessorGenerator
     *            this generator will be used for instrumentation
     * @param cv
     *            next delegate in the visitor chain will receive the
     *            instrumented class
     */
    public ClassInstrumenter(final long id, final IExecutionDataAccessorGenerator accessorGenerator,
            final ClassVisitor cv) {
        super(cv);
        this.id = id;
        this.accessorGenerator = accessorGenerator;
    }

    @Override
    public void visit(final int version, final int access, final String name, final String signature,
            final String superName, final String[] interfaces) {
        this.className = name;
        withFrames = (version & 0xff) >= Opcodes.V1_6;
        if ((access & Opcodes.ACC_INTERFACE) == 0) {
            this.probeArrayStrategy = new ClassTypeStrategy();
        } else {
            this.probeArrayStrategy = new InterfaceTypeStrategy();
        }
        super.visit(version, access, name, signature, superName, interfaces);
    }

    @Override
    public FieldVisitor visitField(final int access, final String name, final String desc, final String signature,
            final Object value) {
        InstrSupport.assertNotInstrumented(name, className);
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodProbesVisitor visitMethod(final int access, final String name, final String desc,
            final String signature, final String[] exceptions) {

        InstrSupport.assertNotInstrumented(name, className);

        final MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);

        if (mv == null) {
            return null;
        }
        final MethodVisitor frameEliminator = new DuplicateFrameEliminator(mv);
        final ProbeInserter probeVariableInserter = new ProbeInserter(access, desc, frameEliminator,
                probeArrayStrategy);
        if (withFrames) {
            final FrameTracker frameTracker = new FrameTracker(className, access, name, desc,
                    probeVariableInserter);
            return new MethodInstrumenter(frameTracker, probeVariableInserter, frameTracker);
        } else {
            return new MethodInstrumenter(probeVariableInserter, probeVariableInserter, IFrameInserter.NOP);
        }
    }

    @Override
    public void visitTotalProbeCount(final int count) {
        probeCount = count;
    }

    @Override
    public void visitEnd() {
        probeArrayStrategy.addMembers(cv);
        super.visitEnd();
    }

    // === probe array strategies ===

    private class ClassTypeStrategy implements IProbeArrayStrategy {

        public int storeInstance(final MethodVisitor mv, final int variable) {
            mv.visitMethodInsn(Opcodes.INVOKESTATIC, className, InstrSupport.INITMETHOD_NAME,
                    InstrSupport.INITMETHOD_DESC);
            mv.visitVarInsn(Opcodes.ASTORE, variable);
            return 1;
        }

        public void addMembers(final ClassVisitor delegate) {
            createDataField();
            createInitMethod(probeCount);
        }

        private void createDataField() {
            cv.visitField(InstrSupport.DATAFIELD_ACC, InstrSupport.DATAFIELD_NAME, InstrSupport.DATAFIELD_DESC,
                    null, null);
        }

        private void createInitMethod(final int probeCount) {
            final MethodVisitor mv = cv.visitMethod(InstrSupport.INITMETHOD_ACC, InstrSupport.INITMETHOD_NAME,
                    InstrSupport.INITMETHOD_DESC, null, null);
            mv.visitCode();

            // Load the value of the static data field:
            mv.visitFieldInsn(Opcodes.GETSTATIC, className, InstrSupport.DATAFIELD_NAME,
                    InstrSupport.DATAFIELD_DESC);
            mv.visitInsn(Opcodes.DUP);

            // Stack[1]: [Z
            // Stack[0]: [Z

            // Skip initialization when we already have a data array:
            final Label alreadyInitialized = new Label();
            mv.visitJumpInsn(Opcodes.IFNONNULL, alreadyInitialized);

            // Stack[0]: [Z

            mv.visitInsn(Opcodes.POP);
            final int size = genInitializeDataField(mv, probeCount);

            // Stack[0]: [Z

            // Return the class' probe array:
            if (withFrames) {
                mv.visitFrame(Opcodes.F_NEW, 0, NO_LOCALS, 1, STACK_ARRZ);
            }
            mv.visitLabel(alreadyInitialized);
            mv.visitInsn(Opcodes.ARETURN);

            mv.visitMaxs(Math.max(size, 2), 0); // Maximum local stack size is 2
            mv.visitEnd();
        }

        /**
         * Generates the byte code to initialize the static coverage data field
         * within this class.
         * 
         * The code will push the [Z data array on the operand stack.
         * 
         * @param mv
         *            generator to emit code to
         */
        private int genInitializeDataField(final MethodVisitor mv, final int probeCount) {
            final int size = accessorGenerator.generateDataAccessor(id, className, probeCount, mv);

            // Stack[0]: [Z

            mv.visitInsn(Opcodes.DUP);

            // Stack[1]: [Z
            // Stack[0]: [Z

            mv.visitFieldInsn(Opcodes.PUTSTATIC, className, InstrSupport.DATAFIELD_NAME,
                    InstrSupport.DATAFIELD_DESC);

            // Stack[0]: [Z

            return Math.max(size, 2); // Maximum local stack size is 2
        }
    }

    private class InterfaceTypeStrategy implements IProbeArrayStrategy {

        public int storeInstance(final MethodVisitor mv, final int variable) {
            final int maxStack = accessorGenerator.generateDataAccessor(id, className, probeCount, mv);
            mv.visitVarInsn(Opcodes.ASTORE, variable);
            return maxStack;
        }

        public void addMembers(final ClassVisitor delegate) {
            // nothing to do
        }

    }

}