• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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