1 /******************************************************************************* 2 * Copyright (c) 2009, 2017 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.internal.Java9Support; 23 import org.jacoco.core.internal.instr.InstrSupport; 24 import org.objectweb.asm.ClassReader; 25 import org.objectweb.asm.ClassVisitor; 26 import org.objectweb.asm.ClassWriter; 27 import org.objectweb.asm.MethodVisitor; 28 import org.objectweb.asm.Opcodes; 29 30 /** 31 * This {@link IRuntime} implementation works with a modified system class. A 32 * new static field 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 public class ModifiedSystemClassRuntime extends AbstractRuntime { 37 38 private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;"; 39 40 private final Class<?> systemClass; 41 42 private final String systemClassName; 43 44 private final String accessFieldName; 45 46 /** 47 * Creates a new runtime based on the given class and members. 48 * 49 * @param systemClass 50 * system class that contains the execution data 51 * @param accessFieldName 52 * name of the public static runtime access field 53 * 54 */ ModifiedSystemClassRuntime(final Class<?> systemClass, final String accessFieldName)55 public ModifiedSystemClassRuntime(final Class<?> systemClass, 56 final String accessFieldName) { 57 super(); 58 this.systemClass = systemClass; 59 this.systemClassName = systemClass.getName().replace('.', '/'); 60 this.accessFieldName = accessFieldName; 61 } 62 63 @Override startup(final RuntimeData data)64 public void startup(final RuntimeData data) throws Exception { 65 super.startup(data); 66 final Field field = systemClass.getField(accessFieldName); 67 field.set(null, data); 68 } 69 shutdown()70 public void shutdown() { 71 // nothing to do 72 } 73 generateDataAccessor(final long classid, final String classname, final int probecount, final MethodVisitor mv)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 RuntimeData.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. Member is created with internal default name. 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 */ createFor(final Instrumentation inst, final String className)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 */ createFor(final Instrumentation inst, final String className, final String accessFieldName)120 public static IRuntime createFor(final Instrumentation inst, 121 final String className, final String accessFieldName) 122 throws ClassNotFoundException { 123 final ClassFileTransformer transformer = new ClassFileTransformer() { 124 public byte[] transform(final ClassLoader loader, 125 final String name, final Class<?> classBeingRedefined, 126 final ProtectionDomain protectionDomain, final byte[] source) 127 throws IllegalClassFormatException { 128 if (name.equals(className)) { 129 return instrument(source, accessFieldName); 130 } 131 return null; 132 } 133 }; 134 inst.addTransformer(transformer); 135 final Class<?> clazz = Class.forName(className.replace('/', '.')); 136 inst.removeTransformer(transformer); 137 try { 138 clazz.getField(accessFieldName); 139 } catch (final NoSuchFieldException e) { 140 throw new RuntimeException(format( 141 "Class %s could not be instrumented.", className), e); 142 } 143 return new ModifiedSystemClassRuntime(clazz, accessFieldName); 144 } 145 146 /** 147 * Adds the static data field to the given class 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(Java9Support.downgradeIfRequired(source)); 158 final ClassWriter writer = new ClassWriter(reader, 0); 159 reader.accept(new ClassVisitor(InstrSupport.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