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