net.sourceforge.cobertura.instrument.pass3.InjectCodeClassInstrumenter.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.cobertura.instrument.pass3.InjectCodeClassInstrumenter.java

Source

/*
 * Cobertura - http://cobertura.sourceforge.net/
 *
 * Copyright (C) 2011 Piotr Tabor
 *
 * Note: This file is dual licensed under the GPL and the Apache
 * Source License (so that it can be used from both the main
 * Cobertura classes and the ant tasks).
 *
 * Cobertura is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version.
 *
 * Cobertura is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Cobertura; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

package net.sourceforge.cobertura.instrument.pass3;

import net.sourceforge.cobertura.instrument.AbstractFindTouchPointsClassInstrumenter;
import net.sourceforge.cobertura.instrument.FindTouchPointsMethodAdapter;
import net.sourceforge.cobertura.instrument.tp.ClassMap;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.LocalVariablesSorter;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * <p>This class is responsible for real instrumentation of the user's class.</p>
 * <p/>
 * <p>It uses information acquired
 * by {@link BuildClassMapClassVisitor} ( {@link #classMap} ) and
 * {@link DetectDuplicatedCodeClassVisitor} and injects
 * code snippet provided by {@link CodeProvider} ( {@link #codeProvider} ).</p>
 *
 * @author piotr.tabor@gmail.com
 */
public class InjectCodeClassInstrumenter extends AbstractFindTouchPointsClassInstrumenter {
    /**
     * This class is responsible for injecting code inside 'interesting places' of methods inside instrumented class
     */
    private final InjectCodeTouchPointListener touchPointListener;

    /**
     * {@link ClassMap} generated in previous instrumentation pass by {@link BuildClassMapClassVisitor}
     */
    private final ClassMap classMap;

    /**
     * {@link CodeProvider} used to generate pieces of asm code that is injected into instrumented class.
     * <p/>
     * We are strictly recommending here using {@link FastArrayCodeProvider} instead of {@link AtomicArrayCodeProvider} because
     * of performance.
     */
    private final CodeProvider codeProvider;

    /**
     * When we processing the class we want to now if we processed 'static initialization block' (clinit method).
     * <p/>
     * <p>If there is no such a method in the instrumented class - we will need to generate it at the end</p>
     */
    private boolean wasStaticInitMethodVisited = false;

    private final Set<String> ignoredMethods;

    /**
     * @param cv                 - a listener for code-instrumentation events
     * @param ignoreRegexes       - list of patters of method calls that should be ignored from line-coverage-measurement
     * @param classMap           - map of all interesting places in the class. You should acquire it by {@link BuildClassMapClassVisitor} and remember to
     *                           prepare it using {@link ClassMap#assignCounterIds()} before using it with {@link InjectCodeClassInstrumenter}
     * @param duplicatedLinesMap - map of found duplicates in the class. You should use {@link DetectDuplicatedCodeClassVisitor} to find the duplicated lines.
     */
    public InjectCodeClassInstrumenter(ClassVisitor cv, Collection<Pattern> ignoreRegexes,
            boolean threadsafeRigorous, ClassMap classMap, Map<Integer, Map<Integer, Integer>> duplicatedLinesMap,
            Set<String> ignoredMethods) {
        super(cv, ignoreRegexes, duplicatedLinesMap);
        this.classMap = classMap;
        this.ignoredMethods = ignoredMethods;
        codeProvider = threadsafeRigorous ? new AtomicArrayCodeProvider() : new FastArrayCodeProvider();
        touchPointListener = new InjectCodeTouchPointListener(classMap, codeProvider);
    }

    /**
     * <p>Marks the class 'already instrumented' and injects code connected to the fields that are keeping counters.</p>
     */
    @Override
    public void visit(int version, int access, String name, String signature, String supertype,
            String[] interfaces) {

        super.visit(version, access, name, signature, supertype, interfaces);
        codeProvider.generateCountersField(cv);
    }

    /**
     * <p>Instrumenting a code in a single method. Special conditions for processing 'static initialization block'.</p>
     * <p/>
     * <p>This method also uses {@link ShiftVariableMethodAdapter} that is used firstly to calculate the index of internal
     * variable injected to store information about last 'processed' jump or switch in runtime ( {@link ShiftVariableMethodAdapter#calculateFirstStackVariable(int, String)} ),
     * and then is used to inject code responsible for keeping the variable and shifting (+1) all previously seen variables.
     */
    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        if (ignoredMethods.contains(name + desc)) {
            return mv;
        }
        if ((access & Opcodes.ACC_STATIC) != 0) {
            mv = new GenerateCallCoberturaInitMethodVisitor(mv, classMap.getClassName());
            if ("<clinit>".equals(name)) {
                wasStaticInitMethodVisited = true;
            }
        }
        FindTouchPointsMethodAdapter instrumenter = new FindTouchPointsMethodAdapter(mv, classMap.getClassName(),
                name, desc, eventIdGenerator, duplicatedLinesMap, lineIdGenerator);
        instrumenter.setTouchPointListener(touchPointListener);
        instrumenter.setIgnoreRegexp(getIgnoreRegexp());
        LocalVariablesSorter sorter = new LocalVariablesSorter(access, desc, instrumenter);
        int variable = sorter.newLocal(Type.INT_TYPE);
        touchPointListener.setLastJumpIdVariableIndex(variable);
        return sorter;
        //return new ShiftVariableMethodAdapter(instrumenter, access, desc, 1);
    }

    /**
     * Method instrumenter that injects {@link CodeProvider#generateCINITmethod(MethodVisitor, String, int)} code, and
     * then forwards the whole previous content of the method.
     *
     * @author piotr.tabor@gmail.com
     */
    private class GenerateCallCoberturaInitMethodVisitor extends MethodVisitor {
        private String className;

        public GenerateCallCoberturaInitMethodVisitor(MethodVisitor arg0, String className) {
            super(Opcodes.ASM4, arg0);
            this.className = className;
        }

        @Override
        public void visitCode() {
            codeProvider.generateCallCoberturaInitMethod(mv, className);
            super.visitCode();
        }
    }

    /**
     * <p>If there was no 'static initialization block' in the class, the method is responsible for generating the method.<br/>
     * It is also responsible for generating method that keeps mapping of counterIds into source places connected to them</p>
     */
    @Override
    public void visitEnd() {
        if (!wasStaticInitMethodVisited) {
            //We need to generate new method
            MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
            mv.visitCode();
            codeProvider.generateCallCoberturaInitMethod(mv, classMap.getClassName());
            mv.visitInsn(Opcodes.RETURN);
            mv.visitMaxs(/*stack*/3, /*local*/0);
            mv.visitEnd();
            wasStaticInitMethodVisited = true;
        }

        codeProvider.generateCoberturaInitMethod(cv, classMap.getClassName(), classMap.getMaxCounterId() + 1);
        codeProvider.generateCoberturaClassMapMethod(cv, classMap);
        codeProvider.generateCoberturaGetAndResetCountersMethod(cv, classMap.getClassName());

        super.visitEnd();
    }

}