1 /******************************************************************************* 2 * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.internal.instr; 13 14 import static java.lang.String.format; 15 16 import org.objectweb.asm.ClassReader; 17 import org.objectweb.asm.MethodVisitor; 18 import org.objectweb.asm.Opcodes; 19 20 /** 21 * Constants and utilities for byte code instrumentation. 22 */ 23 public final class InstrSupport { 24 InstrSupport()25 private InstrSupport() { 26 } 27 28 /** ASM API version */ 29 public static final int ASM_API_VERSION = Opcodes.ASM7; 30 31 // === Data Field === 32 33 /** 34 * Name of the field that stores coverage information of a class. 35 */ 36 public static final String DATAFIELD_NAME = "$jacocoData"; 37 38 /** 39 * Access modifiers of the field that stores coverage information of a 40 * class. 41 * 42 * According to Java Virtual Machine Specification <a href= 43 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.putstatic"> 44 * §6.5.putstatic</a> this field must not be final: 45 * 46 * <blockquote> 47 * <p> 48 * if the field is final, it must be declared in the current class, and the 49 * instruction must occur in the {@code <clinit>} method of the current 50 * class. 51 * </p> 52 * </blockquote> 53 */ 54 public static final int DATAFIELD_ACC = Opcodes.ACC_SYNTHETIC 55 | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT; 56 57 /** 58 * Access modifiers of the field that stores coverage information of a Java 59 * 8 interface. 60 * 61 * According to Java Virtual Machine Specification <a href= 62 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5-200-A.3"> 63 * §4.5</a>: 64 * 65 * <blockquote> 66 * <p> 67 * Fields of interfaces must have their ACC_PUBLIC, ACC_STATIC, and 68 * ACC_FINAL flags set; they may have their ACC_SYNTHETIC flag set and must 69 * not have any of the other flags. 70 * </p> 71 * </blockquote> 72 */ 73 public static final int DATAFIELD_INTF_ACC = Opcodes.ACC_SYNTHETIC 74 | Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL; 75 76 /** 77 * Data type of the field that stores coverage information for a class ( 78 * <code>boolean[]</code>). 79 */ 80 // BEGIN android-change 81 public static final String DATAFIELD_DESC = "Lorg/jacoco/core/data/IExecutionData;"; 82 // END android-change 83 84 // === Init Method === 85 86 /** 87 * Name of the initialization method. 88 */ 89 public static final String INITMETHOD_NAME = "$jacocoInit"; 90 91 /** 92 * Descriptor of the initialization method. 93 */ 94 // BEGIN android-change 95 public static final String INITMETHOD_DESC = "()Lorg/jacoco/core/data/IExecutionData;"; 96 // END android-change 97 98 /** 99 * Access modifiers of the initialization method. 100 */ 101 public static final int INITMETHOD_ACC = Opcodes.ACC_SYNTHETIC 102 | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; 103 104 /** 105 * Name of the interface initialization method. 106 * 107 * According to Java Virtual Machine Specification <a href= 108 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.9-200"> 109 * §2.9</a>: 110 * 111 * <blockquote> 112 * <p> 113 * A class or interface has at most one class or interface initialization 114 * method and is initialized by invoking that method. The initialization 115 * method of a class or interface has the special name {@code <clinit>}, 116 * takes no arguments, and is void. 117 * </p> 118 * <p> 119 * Other methods named {@code <clinit>} in a class file are of no 120 * consequence. They are not class or interface initialization methods. They 121 * cannot be invoked by any Java Virtual Machine instruction and are never 122 * invoked by the Java Virtual Machine itself. 123 * </p> 124 * <p> 125 * In a class file whose version number is 51.0 or above, the method must 126 * additionally have its ACC_STATIC flag set in order to be the class or 127 * interface initialization method. 128 * </p> 129 * <p> 130 * This requirement was introduced in Java SE 7. In a class file whose 131 * version number is 50.0 or below, a method named {@code <clinit>} that is 132 * void and takes no arguments is considered the class or interface 133 * initialization method regardless of the setting of its ACC_STATIC flag. 134 * </p> 135 * </blockquote> 136 * 137 * And <a href= 138 * "https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6-200-A.6"> 139 * §4.6</a>: 140 * 141 * <blockquote> 142 * <p> 143 * Class and interface initialization methods are called implicitly by the 144 * Java Virtual Machine. The value of their access_flags item is ignored 145 * except for the setting of the ACC_STRICT flag. 146 * </p> 147 * </blockquote> 148 */ 149 static final String CLINIT_NAME = "<clinit>"; 150 151 /** 152 * Descriptor of the interface initialization method. 153 * 154 * @see #CLINIT_NAME 155 */ 156 static final String CLINIT_DESC = "()V"; 157 158 /** 159 * Access flags of the interface initialization method generated by JaCoCo. 160 * 161 * @see #CLINIT_NAME 162 */ 163 static final int CLINIT_ACC = Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC; 164 165 /** 166 * Gets major version number from given bytes of class (unsigned two bytes 167 * at offset 6). 168 * 169 * @param b 170 * bytes of class 171 * @return major version of bytecode 172 * @see <a href= 173 * "https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1">Java 174 * Virtual Machine Specification §4 The class File Format</a> 175 * @see #setMajorVersion(int, byte[]) 176 * @see #getMajorVersion(ClassReader) 177 */ getMajorVersion(final byte[] b)178 public static int getMajorVersion(final byte[] b) { 179 return ((b[6] & 0xFF) << 8) | (b[7] & 0xFF); 180 } 181 182 /** 183 * Sets major version number in given bytes of class (unsigned two bytes at 184 * offset 6). 185 * 186 * @param majorVersion 187 * major version of bytecode to set 188 * @param b 189 * bytes of class 190 * @see #getMajorVersion(byte[]) 191 */ setMajorVersion(final int majorVersion, final byte[] b)192 public static void setMajorVersion(final int majorVersion, final byte[] b) { 193 b[6] = (byte) (majorVersion >>> 8); 194 b[7] = (byte) majorVersion; 195 } 196 197 /** 198 * Gets major version number from given {@link ClassReader}. 199 * 200 * @param reader 201 * reader to get information about the class 202 * @return major version of bytecode 203 * @see ClassReader#ClassReader(byte[], int, int) 204 * @see #getMajorVersion(byte[]) 205 */ getMajorVersion(final ClassReader reader)206 public static int getMajorVersion(final ClassReader reader) { 207 // relative to the beginning of constant pool because ASM provides API 208 // to construct ClassReader which reads from the middle of array 209 final int firstConstantPoolEntryOffset = reader.getItem(1) - 1; 210 return reader.readUnsignedShort(firstConstantPoolEntryOffset - 4); 211 } 212 213 /** 214 * Determines whether the given class file version requires stackmap frames. 215 * 216 * @param version 217 * class file version 218 * @return <code>true</code> if frames are required 219 */ needsFrames(final int version)220 public static boolean needsFrames(final int version) { 221 // consider major version only (due to 1.1 anomaly) 222 return (version & 0xFFFF) >= Opcodes.V1_6; 223 } 224 225 /** 226 * Ensures that the given member does not correspond to a internal member 227 * created by the instrumentation process. This would mean that the class is 228 * already instrumented. 229 * 230 * @param member 231 * name of the member to check 232 * @param owner 233 * name of the class owning the member 234 * @throws IllegalStateException 235 * thrown if the member has the same name than the 236 * instrumentation member 237 */ assertNotInstrumented(final String member, final String owner)238 public static void assertNotInstrumented(final String member, 239 final String owner) throws IllegalStateException { 240 if (member.equals(DATAFIELD_NAME) || member.equals(INITMETHOD_NAME)) { 241 throw new IllegalStateException(format( 242 "Cannot process instrumented class %s. Please supply original non-instrumented classes.", 243 owner)); 244 } 245 } 246 247 /** 248 * Generates the instruction to push the given int value on the stack. 249 * Implementation taken from 250 * {@link org.objectweb.asm.commons.GeneratorAdapter#push(int)}. 251 * 252 * @param mv 253 * visitor to emit the instruction 254 * @param value 255 * the value to be pushed on the stack. 256 */ push(final MethodVisitor mv, final int value)257 public static void push(final MethodVisitor mv, final int value) { 258 if (value >= -1 && value <= 5) { 259 mv.visitInsn(Opcodes.ICONST_0 + value); 260 } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 261 mv.visitIntInsn(Opcodes.BIPUSH, value); 262 } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 263 mv.visitIntInsn(Opcodes.SIPUSH, value); 264 } else { 265 mv.visitLdcInsn(Integer.valueOf(value)); 266 } 267 } 268 269 /** 270 * Creates a {@link ClassReader} instance for given bytes of class even if 271 * its version not yet supported by ASM. 272 * 273 * @param b 274 * bytes of class 275 * @return {@link ClassReader} 276 */ classReaderFor(final byte[] b)277 public static ClassReader classReaderFor(final byte[] b) { 278 final int originalVersion = getMajorVersion(b); 279 if (originalVersion == Opcodes.V12 + 1) { 280 // temporarily downgrade version to bypass check in ASM 281 setMajorVersion(Opcodes.V12, b); 282 } 283 final ClassReader classReader = new ClassReader(b); 284 setMajorVersion(originalVersion, b); 285 return classReader; 286 } 287 288 } 289