aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
blob: a805e0ff57b59f458c48f6d08947111d9f3b3e4e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
/*******************************************************************************
 * Copyright (c) 2009, 2021 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.internal.instr;

import static java.lang.String.format;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Constants and utilities for byte code instrumentation.
 */
public final class InstrSupport {

	private InstrSupport() {
	}

	/** ASM API version */
	public static final int ASM_API_VERSION = Opcodes.ASM9;

	// === Data Field ===

	/**
	 * Name of the field that stores coverage information of a class.
	 */
	public static final String DATAFIELD_NAME = "$jacocoData";

	/**
	 * Access modifiers of the field that stores coverage information of a
	 * class.
	 *
	 * According to Java Virtual Machine Specification <a href=
	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.putstatic">
	 * §6.5.putstatic</a> this field must not be final:
	 *
	 * <blockquote>
	 * <p>
	 * if the field is final, it must be declared in the current class, and the
	 * instruction must occur in the {@code <clinit>} method of the current
	 * class.
	 * </p>
	 * </blockquote>
	 */
	public static final int DATAFIELD_ACC = Opcodes.ACC_SYNTHETIC
			| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT;

	/**
	 * Access modifiers of the field that stores coverage information of a Java
	 * 8 interface.
	 *
	 * According to Java Virtual Machine Specification <a href=
	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.3">
	 * §4.5</a>:
	 *
	 * <blockquote>
	 * <p>
	 * Fields of interfaces must have their ACC_PUBLIC, ACC_STATIC, and
	 * ACC_FINAL flags set; they may have their ACC_SYNTHETIC flag set and must
	 * not have any of the other flags.
	 * </p>
	 * </blockquote>
	 */
	public static final int DATAFIELD_INTF_ACC = Opcodes.ACC_SYNTHETIC
			| Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;

	/**
	 * Data type of the field that stores coverage information for a class (
	 * <code>boolean[]</code>).
	 */
	// BEGIN android-change
	public static final String DATAFIELD_DESC_UNQUALIFIED = "org/jacoco/core/data/IExecutionData";
	public static final String DATAFIELD_DESC = "L" + DATAFIELD_DESC_UNQUALIFIED + ";";
	// END android-change

	// === Init Method ===

	/**
	 * Name of the initialization method.
	 */
	public static final String INITMETHOD_NAME = "$jacocoInit";

	/**
	 * Descriptor of the initialization method.
	 */
	// BEGIN android-change
	public static final String INITMETHOD_DESC = "()Lorg/jacoco/core/data/IExecutionData;";
	// END android-change

	/**
	 * Access modifiers of the initialization method.
	 */
	public static final int INITMETHOD_ACC = Opcodes.ACC_SYNTHETIC
			| Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;

	/**
	 * Name of the interface initialization method.
	 *
	 * According to Java Virtual Machine Specification <a href=
	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9-200">
	 * §2.9</a>:
	 *
	 * <blockquote>
	 * <p>
	 * A class or interface has at most one class or interface initialization
	 * method and is initialized by invoking that method. The initialization
	 * method of a class or interface has the special name {@code <clinit>},
	 * takes no arguments, and is void.
	 * </p>
	 * <p>
	 * Other methods named {@code <clinit>} in a class file are of no
	 * consequence. They are not class or interface initialization methods. They
	 * cannot be invoked by any Java Virtual Machine instruction and are never
	 * invoked by the Java Virtual Machine itself.
	 * </p>
	 * <p>
	 * In a class file whose version number is 51.0 or above, the method must
	 * additionally have its ACC_STATIC flag set in order to be the class or
	 * interface initialization method.
	 * </p>
	 * <p>
	 * This requirement was introduced in Java SE 7. In a class file whose
	 * version number is 50.0 or below, a method named {@code <clinit>} that is
	 * void and takes no arguments is considered the class or interface
	 * initialization method regardless of the setting of its ACC_STATIC flag.
	 * </p>
	 * </blockquote>
	 *
	 * And <a href=
	 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6-200-A.6">
	 * §4.6</a>:
	 *
	 * <blockquote>
	 * <p>
	 * Class and interface initialization methods are called implicitly by the
	 * Java Virtual Machine. The value of their access_flags item is ignored
	 * except for the setting of the ACC_STRICT flag.
	 * </p>
	 * </blockquote>
	 */
	static final String CLINIT_NAME = "<clinit>";

	/**
	 * Descriptor of the interface initialization method.
	 *
	 * @see #CLINIT_NAME
	 */
	static final String CLINIT_DESC = "()V";

	/**
	 * Access flags of the interface initialization method generated by JaCoCo.
	 *
	 * @see #CLINIT_NAME
	 */
	static final int CLINIT_ACC = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC;

	/**
	 * Gets major version number from given bytes of class (unsigned two bytes
	 * at offset 6).
	 *
	 * @param b
	 *            bytes of class
	 * @return major version of bytecode
	 * @see <a href=
	 *      "https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1">Java
	 *      Virtual Machine Specification §4 The class File Format</a>
	 * @see #setMajorVersion(int, byte[])
	 * @see #getMajorVersion(ClassReader)
	 */
	public static int getMajorVersion(final byte[] b) {
		return ((b[6] & 0xFF) << 8) | (b[7] & 0xFF);
	}

	/**
	 * Sets major version number in given bytes of class (unsigned two bytes at
	 * offset 6).
	 *
	 * @param majorVersion
	 *            major version of bytecode to set
	 * @param b
	 *            bytes of class
	 * @see #getMajorVersion(byte[])
	 */
	public static void setMajorVersion(final int majorVersion, final byte[] b) {
		b[6] = (byte) (majorVersion >>> 8);
		b[7] = (byte) majorVersion;
	}

	/**
	 * Gets major version number from given {@link ClassReader}.
	 *
	 * @param reader
	 *            reader to get information about the class
	 * @return major version of bytecode
	 * @see ClassReader#ClassReader(byte[], int, int)
	 * @see #getMajorVersion(byte[])
	 */
	public static int getMajorVersion(final ClassReader reader) {
		// relative to the beginning of constant pool because ASM provides API
		// to construct ClassReader which reads from the middle of array
		final int firstConstantPoolEntryOffset = reader.getItem(1) - 1;
		return reader.readUnsignedShort(firstConstantPoolEntryOffset - 4);
	}

	/**
	 * Determines whether the given class file version requires stackmap frames.
	 *
	 * @param version
	 *            class file version
	 * @return <code>true</code> if frames are required
	 */
	public static boolean needsFrames(final int version) {
		// consider major version only (due to 1.1 anomaly)
		return (version & 0xFFFF) >= Opcodes.V1_6;
	}

	/**
	 * Ensures that the given member does not correspond to a internal member
	 * created by the instrumentation process. This would mean that the class is
	 * already instrumented.
	 *
	 * @param member
	 *            name of the member to check
	 * @param owner
	 *            name of the class owning the member
	 * @throws IllegalStateException
	 *             thrown if the member has the same name than the
	 *             instrumentation member
	 */
	public static void assertNotInstrumented(final String member,
			final String owner) throws IllegalStateException {
		if (member.equals(DATAFIELD_NAME) || member.equals(INITMETHOD_NAME)) {
			throw new IllegalStateException(format(
					"Cannot process instrumented class %s. Please supply original non-instrumented classes.",
					owner));
		}
	}

	/**
	 * Generates the instruction to push the given int value on the stack.
	 * Implementation taken from
	 * {@link org.objectweb.asm.commons.GeneratorAdapter#push(int)}.
	 *
	 * @param mv
	 *            visitor to emit the instruction
	 * @param value
	 *            the value to be pushed on the stack.
	 */
	public static void push(final MethodVisitor mv, final int value) {
		if (value >= -1 && value <= 5) {
			mv.visitInsn(Opcodes.ICONST_0 + value);
		} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
			mv.visitIntInsn(Opcodes.BIPUSH, value);
		} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
			mv.visitIntInsn(Opcodes.SIPUSH, value);
		} else {
			mv.visitLdcInsn(Integer.valueOf(value));
		}
	}

	/**
	 * Creates a {@link ClassReader} instance for given bytes of class even if
	 * its version not yet supported by ASM.
	 *
	 * @param b
	 *            bytes of class
	 * @return {@link ClassReader}
	 */
	public static ClassReader classReaderFor(final byte[] b) {
		final int originalVersion = getMajorVersion(b);
		if (originalVersion == Opcodes.V16 + 1) {
			// temporarily downgrade version to bypass check in ASM
			setMajorVersion(Opcodes.V16, b);
		}
		final ClassReader classReader = new ClassReader(b);
		setMajorVersion(originalVersion, b);
		return classReader;
	}

}