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