1 /******************************************************************************* 2 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors 3 * This program and the accompanying materials are made available under 4 * the terms of the Eclipse Public License 2.0 which is available at 5 * http://www.eclipse.org/legal/epl-2.0 6 * 7 * SPDX-License-Identifier: EPL-2.0 8 * 9 * Contributors: 10 * Marc R. Hoffmann - initial API and implementation 11 * 12 *******************************************************************************/ 13 package org.jacoco.core.runtime; 14 15 import static java.lang.String.format; 16 17 import java.lang.instrument.ClassFileTransformer; 18 import java.lang.instrument.IllegalClassFormatException; 19 import java.lang.instrument.Instrumentation; 20 import java.lang.reflect.Field; 21 import java.security.ProtectionDomain; 22 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 * if 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, final String name, 125 final Class<?> classBeingRedefined, 126 final ProtectionDomain protectionDomain, 127 final byte[] source) 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( 141 format("Class %s could not be instrumented.", className), 142 e); 143 } 144 return new ModifiedSystemClassRuntime(clazz, accessFieldName); 145 } 146 147 /** 148 * Adds the static data field to the given class definition. 149 * 150 * @param source 151 * class definition source 152 * @param accessFieldName 153 * name of the runtime access field 154 * @return instrumented version with added members 155 */ instrument(final byte[] source, final String accessFieldName)156 public static byte[] instrument(final byte[] source, 157 final String accessFieldName) { 158 final ClassReader reader = InstrSupport.classReaderFor(source); 159 final ClassWriter writer = new ClassWriter(reader, 0); 160 reader.accept(new ClassVisitor(InstrSupport.ASM_API_VERSION, writer) { 161 162 @Override 163 public void visitEnd() { 164 createDataField(cv, accessFieldName); 165 super.visitEnd(); 166 } 167 168 }, ClassReader.EXPAND_FRAMES); 169 return writer.toByteArray(); 170 } 171 createDataField(final ClassVisitor visitor, final String dataField)172 private static void createDataField(final ClassVisitor visitor, 173 final String dataField) { 174 visitor.visitField( 175 Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC 176 | Opcodes.ACC_TRANSIENT, 177 dataField, ACCESS_FIELD_TYPE, null, null); 178 } 179 180 } 181