All Downloads are FREE. Search and download functionalities are using the official Maven repository.

mockit.asm.methods.MethodReader Maven / Gradle / Ivy

Go to download

JMockit is a Java toolkit for automated developer testing. It contains APIs for the creation of the objects to be tested, for mocking dependencies, and for faking external APIs; JUnit (4 & 5) and TestNG test runners are supported. It also contains an advanced code coverage tool.

The newest version!
package mockit.asm.methods;

import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.FIELDORMETH;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.IINC_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.IMPLVAR;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.INDYMETH;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.ITFMETH;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LABEL;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LABELW;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LDCW_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LDC_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.LOOK_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.MANA_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.NOARG;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.SBYTE;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.SHORT;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.TABL_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.TYPE_INSN;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.VAR;
import static mockit.asm.jvmConstants.JVMInstruction.InstructionType.WIDE_INSN;
import static mockit.asm.jvmConstants.Opcodes.IINC;
import static mockit.asm.jvmConstants.Opcodes.ILOAD;
import static mockit.asm.jvmConstants.Opcodes.ILOAD_0;
import static mockit.asm.jvmConstants.Opcodes.INVOKEINTERFACE;
import static mockit.asm.jvmConstants.Opcodes.INVOKEVIRTUAL;
import static mockit.asm.jvmConstants.Opcodes.ISTORE;
import static mockit.asm.jvmConstants.Opcodes.ISTORE_0;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

import mockit.asm.AnnotatedReader;
import mockit.asm.annotations.AnnotationVisitor;
import mockit.asm.classes.ClassReader;
import mockit.asm.classes.ClassVisitor;
import mockit.asm.controlFlow.Label;
import mockit.asm.jvmConstants.ConstantPoolTypes;
import mockit.asm.jvmConstants.JVMInstruction;
import mockit.asm.util.MethodHandle;

import org.checkerframework.checker.index.qual.NonNegative;

@SuppressWarnings("OverlyComplexClass")
public final class MethodReader extends AnnotatedReader {
    @NonNull
    private final ClassReader cr;
    @NonNull
    private final ClassVisitor cv;

    @Nullable
    private String[] throwsClauseTypes;

    /**
     * The name of the method currently being parsed.
     */
    private String name;

    /**
     * The descriptor of the method currently being parsed.
     */
    private String desc;

    @NonNegative
    private int methodStartCodeIndex;
    @NonNegative
    private int bodyStartCodeIndex;
    @NonNegative
    private int parameterAnnotationsCodeIndex;

    /**
     * The label objects, indexed by bytecode offset, of the method currently being parsed (only bytecode offsets for
     * which a label is needed have a non null associated Label object).
     */
    private Label[] labels;

    /**
     * The visitor to visit the method being read.
     */
    private MethodVisitor mv;

    public MethodReader(@NonNull ClassReader cr, @NonNull ClassVisitor cv) {
        super(cr);
        this.cr = cr;
        this.cv = cv;
    }

    /**
     * Reads each method and constructor in the class, making the {@linkplain #cr class reader}'s
     * {@linkplain ClassReader#cv visitor} visit it.
     *
     * @return the offset of the first byte following the last method in the class
     */
    @NonNegative
    public int readMethods() {
        for (int methodCount = readUnsignedShort(); methodCount > 0; methodCount--) {
            readMethod();
        }

        return codeIndex;
    }

    private void readMethod() {
        readMethodDeclaration();
        parameterAnnotationsCodeIndex = 0;

        readAttributes();

        int currentCodeIndex = codeIndex;
        readMethodBody();
        codeIndex = currentCodeIndex;
    }

    private void readMethodDeclaration() {
        access = readUnsignedShort();
        name = readNonnullUTF8();
        desc = readNonnullUTF8();

        methodStartCodeIndex = codeIndex;
        bodyStartCodeIndex = 0;
        throwsClauseTypes = null;
    }

    @Nullable
    @Override
    protected Boolean readAttribute(@NonNull String attributeName) {
        switch (attributeName) {
            case "Code":
                bodyStartCodeIndex = codeIndex;
                return false;
            case "Exceptions":
                readExceptionsInThrowsClause();
                return true;
            case "RuntimeVisibleParameterAnnotations":
                parameterAnnotationsCodeIndex = codeIndex;
                return false;
            default:
                return null;
        }
    }

    private void readExceptionsInThrowsClause() {
        int n = readUnsignedShort();
        String[] typeDescs = new String[n];

        for (int i = 0; i < n; i++) {
            typeDescs[i] = readNonnullClass();
        }

        throwsClauseTypes = typeDescs;
    }

    private void readMethodBody() {
        mv = cv.visitMethod(access, name, desc, signature, throwsClauseTypes);

        if (mv == null) {
            return;
        }

        if (mv instanceof MethodWriter) {
            copyMethodBody();
            return;
        }

        readAnnotations(mv);
        readAnnotationsOnAllParameters();

        if (bodyStartCodeIndex > 0) {
            codeIndex = bodyStartCodeIndex;
            readCode();
        }

        mv.visitEnd();
    }

    /**
     * If the returned MethodVisitor is in fact a MethodWriter, it means there is no method
     * adapter between the reader and the writer. In addition, it's assumed that the writer's constant pool was copied
     * from this reader (mw.cw.cr == this.cr), and the signature of the method has not been changed; then, we skip all
     * visit events and just copy the original code of the method to the writer.
     */
    private void copyMethodBody() {
        // We do not copy directly the code into MethodWriter to save a byte array copy operation.
        // The real copy will be done in ClassWriter.toByteArray().
        MethodWriter mw = (MethodWriter) mv;
        mw.classReaderOffset = methodStartCodeIndex;
        mw.classReaderLength = codeIndex - methodStartCodeIndex;
    }

    private void readAnnotationsOnAllParameters() {
        if (parameterAnnotationsCodeIndex > 0) {
            codeIndex = parameterAnnotationsCodeIndex;
            int parameters = readUnsignedByte();

            for (int i = 0; i < parameters; i++) {
                readParameterAnnotations(i);
            }
        }
    }

    private void readParameterAnnotations(@NonNegative int parameterIndex) {
        for (int annotationCount = readUnsignedShort(); annotationCount > 0; annotationCount--) {
            String annotationTypeDesc = readNonnullUTF8();
            AnnotationVisitor av = mv.visitParameterAnnotation(parameterIndex, annotationTypeDesc);
            readAnnotationValues(av);
        }
    }

    private void readCode() {
        int maxStack = readUnsignedShort();
        codeIndex += 2; // skip maxLocals

        int codeLength = readInt();
        labels = new Label[codeLength + 2];

        // Reads the bytecode to find the labels.
        int codeStartIndex = codeIndex;
        int codeEndIndex = codeStartIndex + codeLength;

        readAllLabelsInCodeBlock(codeStartIndex, codeEndIndex);
        readTryCatchBlocks();

        // Reads the code attributes.
        int varTableCodeIndex = 0;
        int[] typeTable = null;

        for (int attributeCount = readUnsignedShort(); attributeCount > 0; attributeCount--) {
            String attrName = readNonnullUTF8();
            int codeOffset = readInt();

            switch (attrName) {
                case "LocalVariableTable":
                    varTableCodeIndex = codeIndex;
                    readLocalVariableTable();
                    break;
                case "LocalVariableTypeTable":
                    typeTable = readLocalVariableTypeTable();
                    break;
                case "LineNumberTable":
                    readLineNumberTable();
                    break;
                default:
                    codeIndex += codeOffset;
            }
        }

        readBytecodeInstructionsInCodeBlock(codeStartIndex, codeEndIndex);
        visitEndLabel(codeLength);
        readLocalVariableTables(varTableCodeIndex, typeTable);
        mv.visitMaxStack(maxStack);
    }

    private void readAllLabelsInCodeBlock(@NonNegative int codeStart, @NonNegative int codeEnd) {
        getOrCreateLabel(codeEnd - codeStart + 1);

        while (codeIndex < codeEnd) {
            int offset = codeIndex - codeStart;
            readLabelForInstructionIfAny(offset);
        }
    }

    @NonNull
    private Label getOrCreateLabel(@NonNegative int offset) {
        Label label = labels[offset];

        if (label == null) {
            label = new Label();
            labels[offset] = label;
        }

        return label;
    }

    private void readLabelForInstructionIfAny(@NonNegative int offset) {
        int opcode = readUnsignedByte();
        byte instructionType = JVMInstruction.TYPE[opcode];
        boolean tablInsn = instructionType == TABL_INSN;

        if (tablInsn || instructionType == LOOK_INSN) {
            readLabelsForSwitchInstruction(offset, tablInsn);
        } else {
            readLabelsForNonSwitchInstruction(offset, instructionType);
        }
    }

    private void readLabelsForSwitchInstruction(@NonNegative int offset, boolean tableNotLookup) {
        readSwitchDefaultLabel(offset);

        int caseCount;

        if (tableNotLookup) {
            int min = readInt();
            int max = readInt();
            caseCount = max - min + 1;
        } else {
            caseCount = readInt();
        }

        while (caseCount > 0) {
            if (!tableNotLookup) {
                codeIndex += 4;
            }

            int caseOffset = offset + readInt();
            getOrCreateLabel(caseOffset);
            caseCount--;
        }
    }

    @NonNull
    private Label readSwitchDefaultLabel(@NonNegative int offset) {
        codeIndex += 3 - (offset & 3); // skips 0 to 3 padding bytes

        int defaultLabelOffset = readInt();
        return getOrCreateLabel(offset + defaultLabelOffset);
    }

    @SuppressWarnings("OverlyLongMethod")
    private void readLabelsForNonSwitchInstruction(@NonNegative int offset, byte instructionType) {
        int codeIndexSize = 0;

        // noinspection SwitchStatementWithoutDefaultBranch
        switch (instructionType) {
            case NOARG:
            case IMPLVAR:
                return;
            case LABEL:
                int labelOffset = offset + readShort();
                getOrCreateLabel(labelOffset);
                return;
            case LABELW:
                int labelOffsetW = offset + readInt();
                getOrCreateLabel(labelOffsetW);
                return;
            case WIDE_INSN:
                int opcode = readUnsignedByte();
                codeIndexSize = opcode == IINC ? 4 : 2;
                break;
            case VAR:
            case SBYTE:
            case LDC_INSN:
                codeIndexSize = 1;
                break;
            case SHORT:
            case LDCW_INSN:
            case TYPE_INSN:
            case FIELDORMETH:
            case IINC_INSN:
                codeIndexSize = 2;
                break;
            case ITFMETH:
            case INDYMETH:
                codeIndexSize = 4;
                break;
            case MANA_INSN:
                codeIndexSize = 3;
        }

        codeIndex += codeIndexSize;
    }

    /**
     * Reads the try catch entries to find the labels, and also visits them.
     */
    private void readTryCatchBlocks() {
        for (int blockCount = readUnsignedShort(); blockCount > 0; blockCount--) {
            Label start = getOrCreateLabel(readUnsignedShort());
            Label end = getOrCreateLabel(readUnsignedShort());
            Label handler = getOrCreateLabel(readUnsignedShort());
            String type = readUTF8(readItem());

            mv.visitTryCatchBlock(start, end, handler, type);
        }
    }

    private void readLocalVariableTable() {
        for (int localVarCount = readUnsignedShort(); localVarCount > 0; localVarCount--) {
            int labelOffset = readUnsignedShort();
            getOrCreateDebugLabel(labelOffset);

            labelOffset += readUnsignedShort();
            getOrCreateDebugLabel(labelOffset);

            codeIndex += 6;
        }
    }

    @NonNull
    private Label getOrCreateDebugLabel(@NonNegative int offset) {
        Label label = labels[offset];

        if (label == null) {
            label = new Label();
            label.markAsDebug();
            labels[offset] = label;
        }

        return label;
    }

    @NonNull
    private int[] readLocalVariableTypeTable() {
        int typeTableSize = 3 * readUnsignedShort();
        int[] typeTable = new int[typeTableSize];

        while (typeTableSize > 0) {
            int startIndex = readUnsignedShort();
            int signatureCodeIndex = codeIndex + 4;
            codeIndex += 6;
            int varIndex = readUnsignedShort();

            typeTableSize--;
            typeTable[typeTableSize] = signatureCodeIndex;
            typeTableSize--;
            typeTable[typeTableSize] = varIndex;
            typeTableSize--;
            typeTable[typeTableSize] = startIndex;
        }

        return typeTable;
    }

    private void readLineNumberTable() {
        for (int lineCount = readUnsignedShort(); lineCount > 0; lineCount--) {
            int labelOffset = readUnsignedShort();
            Label debugLabel = getOrCreateDebugLabel(labelOffset);
            debugLabel.line = readUnsignedShort();
        }
    }

    @SuppressWarnings({ "OverlyComplexMethod", "OverlyLongMethod" })
    private void readBytecodeInstructionsInCodeBlock(@NonNegative int codeStartIndex, @NonNegative int codeEndIndex) {
        codeIndex = codeStartIndex;

        while (codeIndex < codeEndIndex) {
            int offset = codeIndex - codeStartIndex;
            visitLabelAndLineNumber(offset);

            int opcode = readUnsignedByte();

            // noinspection SwitchStatementWithoutDefaultBranch
            switch (JVMInstruction.TYPE[opcode]) {
                case NOARG:
                    mv.visitInsn(opcode);
                    break;
                case VAR:
                    readVariableAccessInstruction(opcode);
                    break;
                case IMPLVAR:
                    readInstructionWithImplicitVariable(opcode);
                    break;
                case TYPE_INSN:
                    readTypeInsn(opcode);
                    break;
                case LABEL:
                    readJump(opcode, offset);
                    break;
                case LABELW:
                    readWideJump(opcode, offset);
                    break;
                case LDC_INSN:
                    readLDC();
                    break;
                case LDCW_INSN:
                    readLDCW();
                    break;
                case IINC_INSN:
                    readIInc();
                    break;
                case SBYTE:
                    readInstructionTakingASignedByte(opcode);
                    break;
                case SHORT:
                    readInstructionTakingASignedShort(opcode);
                    break;
                case TABL_INSN:
                    readSwitchInstruction(offset, true);
                    break;
                case LOOK_INSN:
                    readSwitchInstruction(offset, false);
                    break;
                case MANA_INSN:
                    readMultiANewArray();
                    break;
                case WIDE_INSN:
                    readWideInstruction();
                    break;
                case FIELDORMETH:
                case ITFMETH:
                    readFieldOrInvokeInstruction(opcode);
                    break;
                case INDYMETH:
                    readInvokeDynamicInstruction();
                    break;
            }
        }
    }

    private void visitLabelAndLineNumber(@NonNegative int offset) {
        Label label = labels[offset];

        if (label != null) {
            mv.visitLabel(label);

            int lineNumber = label.line;

            if (lineNumber > 0) {
                mv.visitLineNumber(lineNumber, label);
            }
        }
    }

    private void readVariableAccessInstruction(int opcode) {
        int varIndex = readUnsignedByte();
        mv.visitVarInsn(opcode, varIndex);
    }

    private void readInstructionWithImplicitVariable(int opcode) {
        int opcodeBase;

        if (opcode > ISTORE) {
            opcode -= ISTORE_0;
            opcodeBase = ISTORE;
        } else {
            opcode -= ILOAD_0;
            opcodeBase = ILOAD;
        }

        int localVarOpcode = opcodeBase + (opcode >> 2);
        int varIndex = opcode & 3;

        mv.visitVarInsn(localVarOpcode, varIndex);
    }

    private void readTypeInsn(int opcode) {
        String typeDesc = readNonnullClass();
        mv.visitTypeInsn(opcode, typeDesc);
    }

    private void readJump(int opcode, @NonNegative int offset) {
        short targetIndex = readShort();
        Label targetLabel = labels[offset + targetIndex];
        mv.visitJumpInsn(opcode, targetLabel);
    }

    private void readWideJump(int opcode, @NonNegative int offset) {
        int targetIndex = readInt();
        Label targetLabel = labels[offset + targetIndex];
        mv.visitJumpInsn(opcode - 33, targetLabel);
    }

    private void readLDC() {
        int constIndex = readUnsignedByte();
        Object cst = readConst(constIndex);
        mv.visitLdcInsn(cst);
    }

    private void readLDCW() {
        Object cst = readConstItem();
        mv.visitLdcInsn(cst);
    }

    private void readIInc() {
        int varIndex = readUnsignedByte();
        int increment = readSignedByte();
        mv.visitIincInsn(varIndex, increment);
    }

    private void readInstructionTakingASignedByte(int opcode) {
        int operand = readSignedByte();
        mv.visitIntInsn(opcode, operand);
    }

    private void readInstructionTakingASignedShort(int opcode) {
        int operand = readShort();
        mv.visitIntInsn(opcode, operand);
    }

    private void readSwitchInstruction(@NonNegative int offset, boolean tableNotLookup) {
        Label dfltLabel = readSwitchDefaultLabel(offset);
        int min;
        int max;
        int caseCount;
        int[] keys;

        if (tableNotLookup) {
            min = readInt();
            max = readInt();
            caseCount = max - min + 1;
            keys = null;
        } else {
            min = max = 0;
            caseCount = readInt();
            keys = new int[caseCount];
        }

        Label[] handlerLabels = readSwitchCaseLabels(offset, caseCount, keys);

        if (tableNotLookup) {
            mv.visitTableSwitchInsn(min, max, dfltLabel, handlerLabels);
        } else {
            mv.visitLookupSwitchInsn(dfltLabel, keys, handlerLabels);
        }
    }

    @NonNull
    private Label[] readSwitchCaseLabels(@NonNegative int offset, @NonNegative int caseCount, @Nullable int[] keys) {
        Label[] caseLabels = new Label[caseCount];

        for (int i = 0; i < caseCount; i++) {
            if (keys != null) {
                keys[i] = readInt();
            }

            int labelOffset = offset + readInt();
            caseLabels[i] = labels[labelOffset];
        }

        return caseLabels;
    }

    private void readMultiANewArray() {
        String arrayTypeDesc = readNonnullClass();
        int dims = readUnsignedByte();
        mv.visitMultiANewArrayInsn(arrayTypeDesc, dims);
    }

    private void readWideInstruction() {
        int opcode = readUnsignedByte();
        int varIndex = readUnsignedShort();

        if (opcode == IINC) {
            int increment = readShort();
            mv.visitIincInsn(varIndex, increment);
        } else {
            mv.visitVarInsn(opcode, varIndex);
            codeIndex += 2;
        }
    }

    private void readFieldOrInvokeInstruction(int opcode) {
        int ownerCodeIndex = readItem();
        String owner = readNonnullClass(ownerCodeIndex);
        int nameCodeIndex = readItem(ownerCodeIndex + 2);
        String memberName = readNonnullUTF8(nameCodeIndex);
        String memberDesc = readNonnullUTF8(nameCodeIndex + 2);

        if (opcode < INVOKEVIRTUAL) {
            mv.visitFieldInsn(opcode, owner, memberName, memberDesc);
        } else {
            boolean itf = code[ownerCodeIndex - 1] == ConstantPoolTypes.IMETHOD_REF;
            mv.visitMethodInsn(opcode, owner, memberName, memberDesc, itf);

            if (opcode == INVOKEINTERFACE) {
                codeIndex += 2;
            }
        }
    }

    private void readInvokeDynamicInstruction() {
        int cpIndex = readItem();
        int bsmStartIndex = readUnsignedShort(cpIndex);
        int nameCodeIndex = readItem(cpIndex + 2);

        String bsmName = readNonnullUTF8(nameCodeIndex);
        String bsmDesc = readNonnullUTF8(nameCodeIndex + 2);

        int bsmCodeIndex = cr.getBSMCodeIndex(bsmStartIndex);
        MethodHandle bsmHandle = readMethodHandleItem(bsmCodeIndex);
        int bsmArgCount = readUnsignedShort(bsmCodeIndex + 2);
        bsmCodeIndex += 4;
        Object[] bsmArgs = new Object[bsmArgCount];

        for (int i = 0; i < bsmArgCount; i++) {
            bsmArgs[i] = readConstItem(bsmCodeIndex);
            bsmCodeIndex += 2;
        }

        mv.visitInvokeDynamicInsn(bsmName, bsmDesc, bsmHandle, bsmArgs);
        codeIndex += 2;
    }

    private void visitEndLabel(@NonNegative int codeLength) {
        Label label = labels[codeLength];

        if (label != null) {
            mv.visitLabel(label);
        }
    }

    private void readLocalVariableTables(@NonNegative int varTableCodeIndex, @Nullable int[] typeTable) {
        if (varTableCodeIndex > 0) {
            codeIndex = varTableCodeIndex;

            for (int localVarCount = readUnsignedShort(); localVarCount > 0; localVarCount--) {
                int start = readUnsignedShort();
                int length = readUnsignedShort();
                String varName = readNonnullUTF8();
                String varDesc = readNonnullUTF8();
                int index = readUnsignedShort();
                String varSignature = typeTable == null ? null : getLocalVariableSignature(typeTable, start, index);

                mv.visitLocalVariable(varName, varDesc, varSignature, labels[start], labels[start + length], index);
            }
        }
    }

    @Nullable
    private String getLocalVariableSignature(@NonNull int[] typeTable, @NonNegative int start, @NonNegative int index) {
        for (int i = 0, n = typeTable.length; i < n; i += 3) {
            if (typeTable[i] == start && typeTable[i + 1] == index) {
                return readNonnullUTF8(typeTable[i + 2]);
            }
        }

        return null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy