ModifiedSystemClassRuntime.java
/*******************************************************************************
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marc R. Hoffmann - initial API and implementation
*
*******************************************************************************/
package org.jacoco.core.runtime;
import static java.lang.String.format;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import org.jacoco.core.internal.instr.InstrSupport;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* This {@link IRuntime} implementation works with a modified system class. A
* new static field is added to a bootstrap class that will be used by
* instrumented classes. As the system class itself needs to be instrumented
* this runtime requires a Java agent.
*/
public class ModifiedSystemClassRuntime extends AbstractRuntime {
private static final String ACCESS_FIELD_TYPE = "Ljava/lang/Object;";
private final Class<?> systemClass;
private final String systemClassName;
private final String accessFieldName;
/**
* Creates a new runtime based on the given class and members.
*
* @param systemClass
* system class that contains the execution data
* @param accessFieldName
* name of the public static runtime access field
*
*/
public ModifiedSystemClassRuntime(final Class<?> systemClass,
final String accessFieldName) {
super();
this.systemClass = systemClass;
this.systemClassName = systemClass.getName().replace('.', '/');
this.accessFieldName = accessFieldName;
}
@Override
public void startup(final RuntimeData data) throws Exception {
super.startup(data);
final Field field = systemClass.getField(accessFieldName);
field.set(null, data);
}
public void shutdown() {
// nothing to do
}
public int generateDataAccessor(final long classid, final String classname,
final int probecount, final MethodVisitor mv) {
mv.visitFieldInsn(Opcodes.GETSTATIC, systemClassName, accessFieldName,
ACCESS_FIELD_TYPE);
RuntimeData.generateAccessCall(classid, classname, probecount, mv);
return 6;
}
/**
* Creates a new {@link ModifiedSystemClassRuntime} using the given class as
* the data container. Member is created with internal default name. The
* given class must not have been loaded before by the agent.
*
* @param inst
* instrumentation interface
* @param className
* VM name of the class to use
* @return new runtime instance
*
* @throws ClassNotFoundException
* id the given class can not be found
*/
public static IRuntime createFor(final Instrumentation inst,
final String className) throws ClassNotFoundException {
return createFor(inst, className, "$jacocoAccess");
}
/**
* Creates a new {@link ModifiedSystemClassRuntime} using the given class as
* the data container. The given class must not have been loaded before by
* the agent.
*
* @param inst
* instrumentation interface
* @param className
* VM name of the class to use
* @param accessFieldName
* name of the added runtime access field
* @return new runtime instance
*
* @throws ClassNotFoundException
* if the given class can not be found
*/
public static IRuntime createFor(final Instrumentation inst,
final String className, final String accessFieldName)
throws ClassNotFoundException {
final ClassFileTransformer transformer = new ClassFileTransformer() {
public byte[] transform(final ClassLoader loader, final String name,
final Class<?> classBeingRedefined,
final ProtectionDomain protectionDomain,
final byte[] source) throws IllegalClassFormatException {
if (name.equals(className)) {
return instrument(source, accessFieldName);
}
return null;
}
};
inst.addTransformer(transformer);
final Class<?> clazz = Class.forName(className.replace('/', '.'));
inst.removeTransformer(transformer);
try {
clazz.getField(accessFieldName);
} catch (final NoSuchFieldException e) {
throw new RuntimeException(
format("Class %s could not be instrumented.", className),
e);
}
return new ModifiedSystemClassRuntime(clazz, accessFieldName);
}
/**
* Adds the static data field to the given class definition.
*
* @param source
* class definition source
* @param accessFieldName
* name of the runtime access field
* @return instrumented version with added members
*/
public static byte[] instrument(final byte[] source,
final String accessFieldName) {
final ClassReader reader = InstrSupport.classReaderFor(source);
final ClassWriter writer = new ClassWriter(reader, 0);
reader.accept(new ClassVisitor(InstrSupport.ASM_API_VERSION, writer) {
@Override
public void visitEnd() {
createDataField(cv, accessFieldName);
super.visitEnd();
}
}, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}
private static void createDataField(final ClassVisitor visitor,
final String dataField) {
visitor.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC
| Opcodes.ACC_TRANSIENT,
dataField, ACCESS_FIELD_TYPE, null, null);
}
}