ModifiedSystemClassRuntime.java revision 9263b7be8bd812031913c9c284613ee49e06699c
1/*******************************************************************************
2 * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and others
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.runtime;
14
15import static java.lang.String.format;
16
17import java.lang.instrument.ClassFileTransformer;
18import java.lang.instrument.IllegalClassFormatException;
19import java.lang.instrument.Instrumentation;
20import java.lang.reflect.Field;
21import java.security.ProtectionDomain;
22
23import org.objectweb.asm.ClassAdapter;
24import org.objectweb.asm.ClassReader;
25import org.objectweb.asm.ClassVisitor;
26import org.objectweb.asm.ClassWriter;
27import org.objectweb.asm.MethodVisitor;
28import org.objectweb.asm.Opcodes;
29
30/**
31 * This {@link IRuntime} implementation works with a modified system class. A
32 * new static method is added to a bootstrap class that will be used by
33 * instrumented classes. As the system class itself needs to be instrumented
34 * this runtime requires a Java agent.
35 *
36 * @author Marc R. Hoffmann
37 * @version $Revision: $
38 */
39public class ModifiedSystemClassRuntime extends AbstractRuntime {
40
41	private final Class<?> systemClass;
42
43	private final String systemClassName;
44
45	private final String accessMethod;
46
47	private final String dataField;
48
49	/**
50	 * Creates a new runtime based on the given class and members.
51	 *
52	 * @param systemClass
53	 *            system class that contains the execution data
54	 * @param accessMethod
55	 *            name of the public static access method
56	 * @param dataField
57	 *            name of the public static data field
58	 *
59	 */
60	public ModifiedSystemClassRuntime(final Class<?> systemClass,
61			final String accessMethod, final String dataField) {
62		this.systemClass = systemClass;
63		this.systemClassName = systemClass.getName().replace('.', '/');
64		this.accessMethod = accessMethod;
65		this.dataField = dataField;
66	}
67
68	public void startup() throws Exception {
69		final Field field = systemClass.getField(dataField);
70		field.set(null, new MapAdapter(store));
71	}
72
73	public void shutdown() {
74		// nothing to do
75	}
76
77	public int generateDataAccessor(final long classid, final MethodVisitor mv) {
78
79		mv.visitLdcInsn(Long.valueOf(classid));
80
81		// Stack[1]: J
82		// Stack[0]: .
83
84		mv.visitMethodInsn(Opcodes.INVOKESTATIC, systemClassName, accessMethod,
85				"(J)[Z");
86
87		// Stack[0]: [Z
88
89		return 2;
90	}
91
92	/**
93	 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
94	 * the data container. Members are creates with internal default names. The
95	 * given class must not have been loaded before by the agent.
96	 *
97	 * @param inst
98	 *            instrumentation interface
99	 * @param className
100	 *            VM name of the class to use
101	 * @return new runtime instance
102	 *
103	 * @throws ClassNotFoundException
104	 *             id the given class can not be found
105	 */
106	public static IRuntime createFor(final Instrumentation inst,
107			final String className) throws ClassNotFoundException {
108		return createFor(inst, className, "$jacocoGet", "$jacocoData");
109	}
110
111	/**
112	 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
113	 * the data container. The given class must not have been loaded before by
114	 * the agent.
115	 *
116	 * @param inst
117	 *            instrumentation interface
118	 * @param className
119	 *            VM name of the class to use
120	 * @param accessMethod
121	 *            name of the added access method
122	 * @param dataField
123	 *            name of the added data field
124	 * @return new runtime instance
125	 *
126	 * @throws ClassNotFoundException
127	 *             id the given class can not be found
128	 */
129	public static IRuntime createFor(final Instrumentation inst,
130			final String className, final String accessMethod,
131			final String dataField) throws ClassNotFoundException {
132		final boolean[] instrumented = new boolean[] { false };
133		final ClassFileTransformer transformer = new ClassFileTransformer() {
134			public byte[] transform(final ClassLoader loader,
135					final String name, final Class<?> classBeingRedefined,
136					final ProtectionDomain protectionDomain, final byte[] source)
137					throws IllegalClassFormatException {
138				if (name.equals(className)) {
139					instrumented[0] = true;
140					return instrument(source, accessMethod, dataField);
141				}
142				return null;
143			}
144		};
145		inst.addTransformer(transformer);
146		final Class<?> clazz = Class.forName(className.replace('/', '.'));
147		inst.removeTransformer(transformer);
148		if (!instrumented[0]) {
149			final String msg = format("Class %s was not loaded.", className);
150			throw new RuntimeException(msg);
151		}
152		return new ModifiedSystemClassRuntime(clazz, accessMethod, dataField);
153	}
154
155	/**
156	 * Adds the static access method and data field to the given class
157	 * definition.
158	 *
159	 * @param source
160	 *            class definition source
161	 * @param accessMethod
162	 *            name of the access method
163	 * @param dataField
164	 *            name of the data field
165	 * @return instrumented version with added members
166	 */
167	public static byte[] instrument(final byte[] source,
168			final String accessMethod, final String dataField) {
169		final ClassReader reader = new ClassReader(source);
170		final ClassWriter writer = new ClassWriter(reader, 0);
171		reader.accept(new ClassAdapter(writer) {
172
173			private String className;
174
175			@Override
176			public void visit(final int version, final int access,
177					final String name, final String signature,
178					final String superName, final String[] interfaces) {
179				className = name;
180				super.visit(version, access, name, signature, superName,
181						interfaces);
182			}
183
184			@Override
185			public void visitEnd() {
186				createDataField(cv, dataField);
187				createAccessMethod(cv, className, accessMethod, dataField);
188				super.visitEnd();
189			}
190
191		}, ClassReader.EXPAND_FRAMES);
192		return writer.toByteArray();
193	}
194
195	private static void createDataField(final ClassVisitor visitor,
196			final String dataField) {
197		visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,
198				"Ljava/util/Map;", "Ljava/util/Map<Ljava/lang/Long;[Z>;", null);
199	}
200
201	private static void createAccessMethod(final ClassVisitor visitor,
202			final String className, final String accessMethod,
203			final String dataField) {
204		final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
205		final String desc = "(J)[Z";
206		final MethodVisitor mv = visitor.visitMethod(access, accessMethod,
207				desc, null, null);
208
209		mv.visitFieldInsn(Opcodes.GETSTATIC, className, dataField,
210				"Ljava/util/Map;");
211
212		// Stack[0]: Ljava/util/Map;
213
214		mv.visitVarInsn(Opcodes.LLOAD, 0);
215
216		// Stack[2]: J
217		// Stack[1]: .
218		// Stack[0]: Ljava/util/Map;
219
220		mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf",
221				"(J)Ljava/lang/Long;");
222
223		// Stack[1]: Ljava/lang/Long;
224		// Stack[0]: Ljava/util/Map;
225
226		mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/Map", "get",
227				"(Ljava/lang/Object;)Ljava/lang/Object;");
228
229		// Stack[0]: Ljava/lang/Object;
230
231		mv.visitTypeInsn(Opcodes.CHECKCAST, "[Z");
232
233		// Stack[0]: [Z
234
235		mv.visitInsn(Opcodes.ARETURN);
236		mv.visitMaxs(3, 2);
237		mv.visitEnd();
238	}
239
240}
241