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