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