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 * $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 static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";
42
43 private final Class<?> systemClass;
44
45 private final String systemClassName;
46
47 private final String accessFieldName;
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 accessFieldName
55 * name of the public static runtime access field
56 *
57 */
58 public ModifiedSystemClassRuntime(final Class<?> systemClass,
59 final String accessFieldName) {
60 this.systemClass = systemClass;
61 this.systemClassName = systemClass.getName().replace('.', '/');
62 this.accessFieldName = accessFieldName;
63 }
64
65 public void startup() throws Exception {
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, mv);
81
82 return 6;
83 }
84
85 /**
86 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
87 * the data container. Members are creates with internal default names. The
88 * given class must not have been loaded before by the agent.
89 *
90 * @param inst
91 * instrumentation interface
92 * @param className
93 * VM name of the class to use
94 * @return new runtime instance
95 *
96 * @throws ClassNotFoundException
97 * id the given class can not be found
98 */
99 public static IRuntime createFor(final Instrumentation inst,
100 final String className) throws ClassNotFoundException {
101 return createFor(inst, className, "$jacocoAccess");
102 }
103
104 /**
105 * Creates a new {@link ModifiedSystemClassRuntime} using the given class as
106 * the data container. The given class must not have been loaded before by
107 * the agent.
108 *
109 * @param inst
110 * instrumentation interface
111 * @param className
112 * VM name of the class to use
113 * @param accessFieldName
114 * name of the added runtime access field
115 * @return new runtime instance
116 *
117 * @throws ClassNotFoundException
118 * id the given class can not be found
119 */
120 public static IRuntime createFor(final Instrumentation inst,
121 final String className, final String accessFieldName)
122 throws ClassNotFoundException {
123 final boolean[] instrumented = new boolean[] { false };
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 instrumented[0] = true;
131 return instrument(source, accessFieldName);
132 }
133 return null;
134 }
135 };
136 inst.addTransformer(transformer);
137 final Class<?> clazz = Class.forName(className.replace('/', '.'));
138 inst.removeTransformer(transformer);
139 if (!instrumented[0]) {
140 final String msg = format("Class %s was not loaded.", className);
141 throw new RuntimeException(msg);
142 }
143 return new ModifiedSystemClassRuntime(clazz, accessFieldName);
144 }
145
146 /**
147 * Adds the static access method and data field to the given class
148 * definition.
149 *
150 * @param source
151 * class definition source
152 * @param accessFieldName
153 * name of the runtime access field
154 * @return instrumented version with added members
155 */
156 public static byte[] instrument(final byte[] source,
157 final String accessFieldName) {
158 final ClassReader reader = new ClassReader(source);
159 final ClassWriter writer = new ClassWriter(reader, 0);
160 reader.accept(new ClassAdapter(writer) {
161
162 @Override
163 public void visitEnd() {
164 createDataField(cv, accessFieldName);
165 super.visitEnd();
166 }
167
168 }, ClassReader.EXPAND_FRAMES);
169 return writer.toByteArray();
170 }
171
172 private static void createDataField(final ClassVisitor visitor,
173 final String dataField) {
174 visitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, dataField,
175 ACCESS_FIELD_TYPE, null, null);
176 }
177
178}