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