ModifiedSystemClassRuntime.java
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}