• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.internal.instr;
14 
15 import static java.lang.String.format;
16 
17 import org.objectweb.asm.ClassReader;
18 import org.objectweb.asm.MethodVisitor;
19 import org.objectweb.asm.Opcodes;
20 
21 /**
22  * Constants and utilities for byte code instrumentation.
23  */
24 public final class InstrSupport {
25 
InstrSupport()26 	private InstrSupport() {
27 	}
28 
29 	/** ASM API version */
30 	public static final int ASM_API_VERSION = Opcodes.ASM9;
31 
32 	// === Data Field ===
33 
34 	/**
35 	 * Name of the field that stores coverage information of a class.
36 	 */
37 	public static final String DATAFIELD_NAME = "$jacocoData";
38 
39 	/**
40 	 * Access modifiers of the field that stores coverage information of a
41 	 * class.
42 	 *
43 	 * According to Java Virtual Machine Specification <a href=
44 	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.putstatic">
45 	 * §6.5.putstatic</a> this field must not be final:
46 	 *
47 	 * <blockquote>
48 	 * <p>
49 	 * if the field is final, it must be declared in the current class, and the
50 	 * instruction must occur in the {@code <clinit>} method of the current
51 	 * class.
52 	 * </p>
53 	 * </blockquote>
54 	 */
55 	public static final int DATAFIELD_ACC = Opcodes.ACC_SYNTHETIC
56 			| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT;
57 
58 	/**
59 	 * Access modifiers of the field that stores coverage information of a Java
60 	 * 8 interface.
61 	 *
62 	 * According to Java Virtual Machine Specification <a href=
63 	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.3">
64 	 * §4.5</a>:
65 	 *
66 	 * <blockquote>
67 	 * <p>
68 	 * Fields of interfaces must have their ACC_PUBLIC, ACC_STATIC, and
69 	 * ACC_FINAL flags set; they may have their ACC_SYNTHETIC flag set and must
70 	 * not have any of the other flags.
71 	 * </p>
72 	 * </blockquote>
73 	 */
74 	public static final int DATAFIELD_INTF_ACC = Opcodes.ACC_SYNTHETIC
75 			| Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
76 
77 	/**
78 	 * Data type of the field that stores coverage information for a class (
79 	 * <code>boolean[]</code>).
80 	 */
81 	// BEGIN android-change
82 	public static final String DATAFIELD_DESC = "Lorg/jacoco/core/data/IExecutionData;";
83 	// END android-change
84 
85 	// === Init Method ===
86 
87 	/**
88 	 * Name of the initialization method.
89 	 */
90 	public static final String INITMETHOD_NAME = "$jacocoInit";
91 
92 	/**
93 	 * Descriptor of the initialization method.
94 	 */
95 	// BEGIN android-change
96 	public static final String INITMETHOD_DESC = "()Lorg/jacoco/core/data/IExecutionData;";
97 	// END android-change
98 
99 	/**
100 	 * Access modifiers of the initialization method.
101 	 */
102 	public static final int INITMETHOD_ACC = Opcodes.ACC_SYNTHETIC
103 			| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
104 
105 	/**
106 	 * Name of the interface initialization method.
107 	 *
108 	 * According to Java Virtual Machine Specification <a href=
109 	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9-200">
110 	 * §2.9</a>:
111 	 *
112 	 * <blockquote>
113 	 * <p>
114 	 * A class or interface has at most one class or interface initialization
115 	 * method and is initialized by invoking that method. The initialization
116 	 * method of a class or interface has the special name {@code <clinit>},
117 	 * takes no arguments, and is void.
118 	 * </p>
119 	 * <p>
120 	 * Other methods named {@code <clinit>} in a class file are of no
121 	 * consequence. They are not class or interface initialization methods. They
122 	 * cannot be invoked by any Java Virtual Machine instruction and are never
123 	 * invoked by the Java Virtual Machine itself.
124 	 * </p>
125 	 * <p>
126 	 * In a class file whose version number is 51.0 or above, the method must
127 	 * additionally have its ACC_STATIC flag set in order to be the class or
128 	 * interface initialization method.
129 	 * </p>
130 	 * <p>
131 	 * This requirement was introduced in Java SE 7. In a class file whose
132 	 * version number is 50.0 or below, a method named {@code <clinit>} that is
133 	 * void and takes no arguments is considered the class or interface
134 	 * initialization method regardless of the setting of its ACC_STATIC flag.
135 	 * </p>
136 	 * </blockquote>
137 	 *
138 	 * And <a href=
139 	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6-200-A.6">
140 	 * §4.6</a>:
141 	 *
142 	 * <blockquote>
143 	 * <p>
144 	 * Class and interface initialization methods are called implicitly by the
145 	 * Java Virtual Machine. The value of their access_flags item is ignored
146 	 * except for the setting of the ACC_STRICT flag.
147 	 * </p>
148 	 * </blockquote>
149 	 */
150 	static final String CLINIT_NAME = "<clinit>";
151 
152 	/**
153 	 * Descriptor of the interface initialization method.
154 	 *
155 	 * @see #CLINIT_NAME
156 	 */
157 	static final String CLINIT_DESC = "()V";
158 
159 	/**
160 	 * Access flags of the interface initialization method generated by JaCoCo.
161 	 *
162 	 * @see #CLINIT_NAME
163 	 */
164 	static final int CLINIT_ACC = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC;
165 
166 	/**
167 	 * Gets major version number from given bytes of class (unsigned two bytes
168 	 * at offset 6).
169 	 *
170 	 * @param b
171 	 *            bytes of class
172 	 * @return major version of bytecode
173 	 * @see <a href=
174 	 *      "https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1">Java
175 	 *      Virtual Machine Specification §4 The class File Format</a>
176 	 * @see #setMajorVersion(int, byte[])
177 	 * @see #getMajorVersion(ClassReader)
178 	 */
getMajorVersion(final byte[] b)179 	public static int getMajorVersion(final byte[] b) {
180 		return ((b[6] & 0xFF) << 8) | (b[7] & 0xFF);
181 	}
182 
183 	/**
184 	 * Sets major version number in given bytes of class (unsigned two bytes at
185 	 * offset 6).
186 	 *
187 	 * @param majorVersion
188 	 *            major version of bytecode to set
189 	 * @param b
190 	 *            bytes of class
191 	 * @see #getMajorVersion(byte[])
192 	 */
setMajorVersion(final int majorVersion, final byte[] b)193 	public static void setMajorVersion(final int majorVersion, final byte[] b) {
194 		b[6] = (byte) (majorVersion >>> 8);
195 		b[7] = (byte) majorVersion;
196 	}
197 
198 	/**
199 	 * Gets major version number from given {@link ClassReader}.
200 	 *
201 	 * @param reader
202 	 *            reader to get information about the class
203 	 * @return major version of bytecode
204 	 * @see ClassReader#ClassReader(byte[], int, int)
205 	 * @see #getMajorVersion(byte[])
206 	 */
getMajorVersion(final ClassReader reader)207 	public static int getMajorVersion(final ClassReader reader) {
208 		// relative to the beginning of constant pool because ASM provides API
209 		// to construct ClassReader which reads from the middle of array
210 		final int firstConstantPoolEntryOffset = reader.getItem(1) - 1;
211 		return reader.readUnsignedShort(firstConstantPoolEntryOffset - 4);
212 	}
213 
214 	/**
215 	 * Determines whether the given class file version requires stackmap frames.
216 	 *
217 	 * @param version
218 	 *            class file version
219 	 * @return <code>true</code> if frames are required
220 	 */
needsFrames(final int version)221 	public static boolean needsFrames(final int version) {
222 		// consider major version only (due to 1.1 anomaly)
223 		return (version & 0xFFFF) >= Opcodes.V1_6;
224 	}
225 
226 	/**
227 	 * Ensures that the given member does not correspond to a internal member
228 	 * created by the instrumentation process. This would mean that the class is
229 	 * already instrumented.
230 	 *
231 	 * @param member
232 	 *            name of the member to check
233 	 * @param owner
234 	 *            name of the class owning the member
235 	 * @throws IllegalStateException
236 	 *             thrown if the member has the same name than the
237 	 *             instrumentation member
238 	 */
assertNotInstrumented(final String member, final String owner)239 	public static void assertNotInstrumented(final String member,
240 			final String owner) throws IllegalStateException {
241 		if (member.equals(DATAFIELD_NAME) || member.equals(INITMETHOD_NAME)) {
242 			throw new IllegalStateException(format(
243 					"Cannot process instrumented class %s. Please supply original non-instrumented classes.",
244 					owner));
245 		}
246 	}
247 
248 	/**
249 	 * Generates the instruction to push the given int value on the stack.
250 	 * Implementation taken from
251 	 * {@link org.objectweb.asm.commons.GeneratorAdapter#push(int)}.
252 	 *
253 	 * @param mv
254 	 *            visitor to emit the instruction
255 	 * @param value
256 	 *            the value to be pushed on the stack.
257 	 */
push(final MethodVisitor mv, final int value)258 	public static void push(final MethodVisitor mv, final int value) {
259 		if (value >= -1 && value <= 5) {
260 			mv.visitInsn(Opcodes.ICONST_0 + value);
261 		} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
262 			mv.visitIntInsn(Opcodes.BIPUSH, value);
263 		} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
264 			mv.visitIntInsn(Opcodes.SIPUSH, value);
265 		} else {
266 			mv.visitLdcInsn(Integer.valueOf(value));
267 		}
268 	}
269 
270 	/**
271 	 * Creates a {@link ClassReader} instance for given bytes of class even if
272 	 * its version not yet supported by ASM.
273 	 *
274 	 * @param b
275 	 *            bytes of class
276 	 * @return {@link ClassReader}
277 	 */
classReaderFor(final byte[] b)278 	public static ClassReader classReaderFor(final byte[] b) {
279 		final int originalVersion = getMajorVersion(b);
280 		if (originalVersion == Opcodes.V16 + 1) {
281 			// temporarily downgrade version to bypass check in ASM
282 			setMajorVersion(Opcodes.V16, b);
283 		}
284 		final ClassReader classReader = new ClassReader(b);
285 		setMajorVersion(originalVersion, b);
286 		return classReader;
287 	}
288 
289 }
290