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