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