aboutsummaryrefslogtreecommitdiff
path: root/org.jacoco.core/src/org/jacoco/core/internal/instr/InstrSupport.java
blob: 7499c97be354084e2554e8220643e127595104e1 (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
/*******************************************************************************
 * Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *    
 *******************************************************************************/
package org.jacoco.core.internal.instr;

import static java.lang.String.format;

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.ASM6;

	// === 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>).
	 */
	public static final String DATAFIELD_DESC = "[Z";

	// === Init Method ===

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

	/**
	 * Descriptor of the initialization method.
	 */
	public static final String INITMETHOD_DESC = "()[Z";

	/**
	 * 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;

	/**
	 * 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(
					"Class %s is already instrumented.", 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));
		}
	}

}