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