org.jacoco.core.runtime.ModifiedSystemClassRuntime.java Source code

Java tutorial

Introduction

Here is the source code for org.jacoco.core.runtime.ModifiedSystemClassRuntime.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.runtime;

import static java.lang.String.format;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * This {@link IRuntime} implementation works with a modified system class. A
 * new static method is added to a bootstrap class that will be used by
 * instrumented classes. As the system class itself needs to be instrumented
 * this runtime requires a Java agent.
 */
public class ModifiedSystemClassRuntime extends AbstractRuntime {

    private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";

    private final Class<?> systemClass;

    private final String systemClassName;

    private final String accessFieldName;

    /**
     * Creates a new runtime based on the given class and members.
     * 
     * @param systemClass
     *            system class that contains the execution data
     * @param accessFieldName
     *            name of the public static runtime access field
     * 
     */
    public ModifiedSystemClassRuntime(final Class<?> systemClass, final String accessFieldName) {
        super();
        this.systemClass = systemClass;
        this.systemClassName = systemClass.getName().replace('.', '/');
        this.accessFieldName = accessFieldName;
    }

    @Override
    public void startup(final RuntimeData data) throws Exception {
        super.startup(data);
        final Field field = systemClass.getField(accessFieldName);
        field.set(null, data);
    }

    public void shutdown() {
        // nothing to do
    }

    public int generateDataAccessor(final long classid, final String classname, final int probecount,
            final MethodVisitor mv) {

        mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName, ACCESS_FIELD_TYPE);

        RuntimeData.generateAccessCall(classid, classname, probecount, mv);

        return 6;
    }

    /**
     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
     * the data container. Members are creates with internal default names. The
     * given class must not have been loaded before by the agent.
     * 
     * @param inst
     *            instrumentation interface
     * @param className
     *            VM name of the class to use
     * @return new runtime instance
     * 
     * @throws ClassNotFoundException
     *             id the given class can not be found
     */
    public static IRuntime createFor(final Instrumentation inst, final String className)
            throws ClassNotFoundException {
        return createFor(inst, className, "$jacocoAccess");
    }

    /**
     * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
     * the data container. The given class must not have been loaded before by
     * the agent.
     * 
     * @param inst
     *            instrumentation interface
     * @param className
     *            VM name of the class to use
     * @param accessFieldName
     *            name of the added runtime access field
     * @return new runtime instance
     * 
     * @throws ClassNotFoundException
     *             id the given class can not be found
     */
    public static IRuntime createFor(final Instrumentation inst, final String className,
            final String accessFieldName) throws ClassNotFoundException {
        final ClassFileTransformer transformer = new ClassFileTransformer() {
            public byte[] transform(final ClassLoader loader, final String name, final Class<?> classBeingRedefined,
                    final ProtectionDomain protectionDomain, final byte[] source)
                    throws IllegalClassFormatException {
                if (name.equals(className)) {
                    return instrument(source, accessFieldName);
                }
                return null;
            }
        };
        inst.addTransformer(transformer);
        final Class<?> clazz = Class.forName(className.replace('/', '.'));
        inst.removeTransformer(transformer);
        try {
            clazz.getField(accessFieldName);
        } catch (final NoSuchFieldException e) {
            throw new RuntimeException(format("Class %s could not be instrumented.", className), e);
        }
        return new ModifiedSystemClassRuntime(clazz, accessFieldName);
    }

    /**
     * Adds the static access method and data field to the given class
     * definition.
     * 
     * @param source
     *            class definition source
     * @param accessFieldName
     *            name of the runtime access field
     * @return instrumented version with added members
     */
    public static byte[] instrument(final byte[] source, final String accessFieldName) {
        final ClassReader reader = new ClassReader(source);
        final ClassWriter writer = new ClassWriter(reader, 0);
        reader.accept(new ClassVisitor(Opcodes.ASM4, writer) {

            @Override
            public void visitEnd() {
                createDataField(cv, accessFieldName);
                super.visitEnd();
            }

        }, ClassReader.EXPAND_FRAMES);
        return writer.toByteArray();
    }

    private static void createDataField(final ClassVisitor visitor, final String dataField) {
        visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT,
                dataField, ACCESS_FIELD_TYPE, null, null);
    }

}