1 /******************************************************************************* 2 * Copyright (c) 2009, 2015 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 *******************************************************************************/ 12 package org.jacoco.core.runtime; 13 14 import static java.lang.String.format; 15 16 import java.lang.instrument.ClassFileTransformer; 17 import java.lang.instrument.IllegalClassFormatException; 18 import java.lang.instrument.Instrumentation; 19 import java.lang.reflect.Field; 20 import java.security.ProtectionDomain; 21 22 import org.jacoco.core.JaCoCo; 23 import org.objectweb.asm.ClassReader; 24 import org.objectweb.asm.ClassVisitor; 25 import org.objectweb.asm.ClassWriter; 26 import org.objectweb.asm.MethodVisitor; 27 import 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 public 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 */ ModifiedSystemClassRuntime(final Class<?> systemClass, final String accessFieldName)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 @Override startup(final RuntimeData data)63 public void startup(final RuntimeData data) throws Exception { 64 super.startup(data); 65 final Field field = systemClass.getField(accessFieldName); 66 field.set(null, data); 67 } 68 shutdown()69 public void shutdown() { 70 // nothing to do 71 } 72 generateDataAccessor(final long classid, final String classname, final int probecount, final MethodVisitor mv)73 public int generateDataAccessor(final long classid, final String classname, 74 final int probecount, final MethodVisitor mv) { 75 76 mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName, 77 ACCESS_FIELD_TYPE); 78 79 RuntimeData.generateAccessCall(classid, classname, probecount, 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 */ createFor(final Instrumentation inst, final String className)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 */ createFor(final Instrumentation inst, final String className, final String accessFieldName)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 */ instrument(final byte[] source, final String accessFieldName)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 ClassVisitor(JaCoCo.ASM_API_VERSION, 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 createDataField(final ClassVisitor visitor, final String dataField)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