ModifiedSystemClassRuntime.java revision 32e3a05444847ecde19cef575e57fec51c85d085
1/*******************************************************************************
2 * Copyright (c) 2009, 2012 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 *******************************************************************************/
12package org.jacoco.core.runtime;
13
14import static java.lang.String.format;
15
16import java.lang.instrument.ClassFileTransformer;
17import java.lang.instrument.IllegalClassFormatException;
18import java.lang.instrument.Instrumentation;
19import java.lang.reflect.Field;
20import java.security.ProtectionDomain;
21
22import org.objectweb.asm.ClassReader;
23import org.objectweb.asm.ClassVisitor;
24import org.objectweb.asm.ClassWriter;
25import org.objectweb.asm.MethodVisitor;
26import org.objectweb.asm.Opcodes;
27
28/**
29 * This {@link IRuntime} implementation works with a modified system class. A
30 * new static method is added to a bootstrap class that will be used by
31 * instrumented classes. As the system class itself needs to be instrumented
32 * this runtime requires a Java agent.
33 */
34public class ModifiedSystemClassRuntime extends AbstractRuntime {
35
36	private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";
37
38	private final Class<?> systemClass;
39
40	private final String systemClassName;
41
42	private final String accessFieldName;
43
44	/**
45	 * Creates a new runtime based on the given class and members.
46	 *
47	 * @param systemClass
48	 *            system class that contains the execution data
49	 * @param accessFieldName
50	 *            name of the public static runtime access field
51	 *
52	 */
53	public ModifiedSystemClassRuntime(final Class<?> systemClass,
54			final String accessFieldName) {
55		super();
56		this.systemClass = systemClass;
57		this.systemClassName = systemClass.getName().replace('.', '/');
58		this.accessFieldName = accessFieldName;
59	}
60
61	public void startup() throws Exception {
62		setStartTimeStamp();
63		final Field field = systemClass.getField(accessFieldName);
64		field.set(null, new ExecutionDataAccess(store));
65	}
66
67	public void shutdown() {
68		// nothing to do
69	}
70
71	public int generateDataAccessor(final long classid, final String classname,
72			final int probecount, final MethodVisitor mv) {
73
74		mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName,
75				ACCESS_FIELD_TYPE);
76
77		ExecutionDataAccess.generateAccessCall(classid, classname, probecount,
78				mv);
79
80		return 6;
81	}
82
83	/**
84	 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
85	 * the data container. Members are creates with internal default names. The
86	 * given class must not have been loaded before by the agent.
87	 *
88	 * @param inst
89	 *            instrumentation interface
90	 * @param className
91	 *            VM name of the class to use
92	 * @return new runtime instance
93	 *
94	 * @throws ClassNotFoundException
95	 *             id the given class can not be found
96	 */
97	public static IRuntime createFor(final Instrumentation inst,
98			final String className) throws ClassNotFoundException {
99		return createFor(inst, className, "$jacocoAccess");
100	}
101
102	/**
103	 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
104	 * the data container. The given class must not have been loaded before by
105	 * the agent.
106	 *
107	 * @param inst
108	 *            instrumentation interface
109	 * @param className
110	 *            VM name of the class to use
111	 * @param accessFieldName
112	 *            name of the added runtime access field
113	 * @return new runtime instance
114	 *
115	 * @throws ClassNotFoundException
116	 *             id the given class can not be found
117	 */
118	public static IRuntime createFor(final Instrumentation inst,
119			final String className, final String accessFieldName)
120			throws ClassNotFoundException {
121		final ClassFileTransformer transformer = new ClassFileTransformer() {
122			public byte[] transform(final ClassLoader loader,
123					final String name, final Class<?> classBeingRedefined,
124					final ProtectionDomain protectionDomain, final byte[] source)
125					throws IllegalClassFormatException {
126				if (name.equals(className)) {
127					return instrument(source, accessFieldName);
128				}
129				return null;
130			}
131		};
132		inst.addTransformer(transformer);
133		final Class<?> clazz = Class.forName(className.replace('/', '.'));
134		inst.removeTransformer(transformer);
135		try {
136			clazz.getField(accessFieldName);
137		} catch (final NoSuchFieldException e) {
138			throw new RuntimeException(format(
139					"Class %s could not be instrumented.", className), e);
140		}
141		return new ModifiedSystemClassRuntime(clazz, accessFieldName);
142	}
143
144	/**
145	 * Adds the static access method and data field to the given class
146	 * definition.
147	 *
148	 * @param source
149	 *            class definition source
150	 * @param accessFieldName
151	 *            name of the runtime access field
152	 * @return instrumented version with added members
153	 */
154	public static byte[] instrument(final byte[] source,
155			final String accessFieldName) {
156		final ClassReader reader = new ClassReader(source);
157		final ClassWriter writer = new ClassWriter(reader, 0);
158		reader.accept(new ClassVisitor(Opcodes.ASM4, writer) {
159
160			@Override
161			public void visitEnd() {
162				createDataField(cv, accessFieldName);
163				super.visitEnd();
164			}
165
166		}, ClassReader.EXPAND_FRAMES);
167		return writer.toByteArray();
168	}
169
170	private static void createDataField(final ClassVisitor visitor,
171			final String dataField) {
172		visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC
173				| Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT, dataField,
174				ACCESS_FIELD_TYPE, null, null);
175	}
176
177}
178