/******************************************************************************* * Copyright (c) 2009, 2019 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.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.ASM7; // === 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 * §6.5.putstatic this field must not be final: * *
*

* if the field is final, it must be declared in the current class, and the * instruction must occur in the {@code } method of the current * class. *

*
*/ 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 * §4.5: * *
*

* 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. *

*
*/ 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 ( * boolean[]). */ 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 * §2.9: * *
*

* 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 }, * takes no arguments, and is void. *

*

* Other methods named {@code } 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. *

*

* 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. *

*

* This requirement was introduced in Java SE 7. In a class file whose * version number is 50.0 or below, a method named {@code } that is * void and takes no arguments is considered the class or interface * initialization method regardless of the setting of its ACC_STATIC flag. *

*
* * And * §4.6: * *
*

* 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. *

*
*/ static final String CLINIT_NAME = ""; /** * 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 Java * Virtual Machine Specification §4 The class File Format * @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 true if frames are required */ public static boolean needsFrames(final int version) { // consider major version only (due to 1.1 anomaly) return (version & 0xff) >= 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.V12 + 1) { // temporarily downgrade version to bypass check in ASM setMajorVersion(Opcodes.V12, b); } final ClassReader classReader = new ClassReader(b); setMajorVersion(originalVersion, b); return classReader; } }