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