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

mockit.asm.metadata.ClassMetadataReader 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!
/*
 * Copyright (c) 2006 JMockit developers
 * This file is subject to the terms of the MIT license (see LICENSE.txt).
 */
package mockit.asm.metadata;

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

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;

import mockit.asm.jvmConstants.Access;

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

public final class ClassMetadataReader extends ObjectWithAttributes {
    private static final Charset UTF8 = Charset.forName("UTF-8");
    private static final ConstantPoolTag[] CONSTANT_POOL_TAGS = ConstantPoolTag.values();

    enum ConstantPoolTag { // values from JVM spec Table 4.4.A
        No0, // 0
        Utf8(2), // 1 (has variable size)
        No2, // 2
        Integer(4), // 3
        Float(4), // 4
        Long(8), // 5
        Double(8), // 6
        Class(2), // 7
        String(2), // 8
        FieldRef(4), // 9
        MethodRef(4), // 10
        InterfaceMethodRef(4), // 11
        NameAndType(4), // 12
        No13, No14, MethodHandle(3), // 15, added in Java 7
        MethodType(2), // 16, added in Java 7
        ConstantDynamic(4), // 17, added in Java 11
        InvokeDynamic(4), // 18, added in Java 7
        Module(2), // 19, added in Java 9
        Package(2); // 20, added in Java 9

        @NonNegative
        final int itemSize;

        ConstantPoolTag() {
            itemSize = 0;
        }

        ConstantPoolTag(@NonNegative int itemSize) {
            this.itemSize = itemSize;
        }
    }

    public enum Attribute {
        Annotations, Parameters, Signature
    }

    @NonNull
    private final byte[] code;
    @NonNull
    private final int[] cpItemCodeIndexes;
    @Nullable
    private final EnumSet attributesToRead;

    /**
     * The constant pool starts at index 10 in the code array; this is the end index, which must be computed as it's not
     * stored anywhere.
     */
    @NonNegative
    private final int cpEndIndex;

    @NonNegative
    private int fieldsEndIndex;
    @NonNegative
    private int methodsEndIndex;

    @NonNegative
    public static int readVersion(@NonNull byte[] code) {
        int byte0 = (code[4] & 0xFF) << 24;
        int byte1 = (code[5] & 0xFF) << 16;
        int byte2 = (code[6] & 0xFF) << 8;
        int byte3 = code[7] & 0xFF;
        return byte0 | byte1 | byte2 | byte3;
    }

    public ClassMetadataReader(@NonNull byte[] code) {
        this(code, null);
    }

    public ClassMetadataReader(@NonNull byte[] code, @Nullable EnumSet attributesToRead) {
        this.code = code;
        int cpItemCount = readUnsignedShort(8);
        int[] cpTable = new int[cpItemCount];
        cpItemCodeIndexes = cpTable;
        this.attributesToRead = attributesToRead;
        cpEndIndex = findEndIndexOfConstantPoolTable(cpTable);
    }

    @NonNegative
    private int readUnsignedShort(@NonNegative int codeIndex) {
        byte[] b = code;
        int i = codeIndex;
        int byte0 = (b[i] & 0xFF) << 8;
        i++;
        int byte1 = b[i] & 0xFF;
        return byte0 | byte1;
    }

    private int readInt(@NonNegative int codeIndex) {
        byte[] b = code;
        int i = codeIndex;
        int byte0 = (b[i] & 0xFF) << 24;
        i++;
        int byte1 = (b[i] & 0xFF) << 16;
        i++;
        int byte2 = (b[i] & 0xFF) << 8;
        i++;
        int byte3 = b[i] & 0xFF;
        return byte0 | byte1 | byte2 | byte3;
    }

    @NonNegative
    private int findEndIndexOfConstantPoolTable(@NonNull int[] cpTable) {
        byte[] b = code;
        int codeIndex = 10;

        for (int cpItemIndex = 1, n = cpTable.length; cpItemIndex < n; cpItemIndex++) {
            int tagValue = b[codeIndex];
            codeIndex++;
            ConstantPoolTag tag = CONSTANT_POOL_TAGS[tagValue];

            cpTable[cpItemIndex] = codeIndex;

            int cpItemSize = tag.itemSize;

            if (tag == ConstantPoolTag.Long || tag == ConstantPoolTag.Double) {
                cpItemIndex++;
            } else if (tag == ConstantPoolTag.Utf8) {
                int stringLength = readUnsignedShort(codeIndex);
                cpItemSize += stringLength;
            }

            codeIndex += cpItemSize;
        }

        return codeIndex;
    }

    @NonNegative
    public int getVersion() {
        return readVersion(code);
    }

    @NonNegative
    public int getAccessFlags() {
        return readUnsignedShort(cpEndIndex);
    }

    @NonNull
    public String getThisClass() {
        int cpClassIndex = readUnsignedShort(cpEndIndex + 2);
        return getTypeDescription(cpClassIndex);
    }

    @NonNull
    private String getTypeDescription(@NonNegative int cpClassIndex) {
        int cpClassCodeIndex = cpItemCodeIndexes[cpClassIndex];
        int cpDescriptionIndex = readUnsignedShort(cpClassCodeIndex);
        return getString(cpDescriptionIndex);
    }

    @NonNull
    private String getString(@NonNegative int cpStringIndex) {
        int codeIndex = cpItemCodeIndexes[cpStringIndex];
        int stringLength = readUnsignedShort(codeIndex);
        return new String(code, codeIndex + 2, stringLength, UTF8);
    }

    @Nullable
    public String getSuperClass() {
        int cpClassIndex = readUnsignedShort(cpEndIndex + 4);

        if (cpClassIndex == 0) {
            return null;
        }

        return getTypeDescription(cpClassIndex);
    }

    @Nullable
    public String[] getInterfaces() {
        int codeIndex = cpEndIndex + 6;
        int interfaceCount = readUnsignedShort(codeIndex);

        if (interfaceCount == 0) {
            return null;
        }

        codeIndex += 2;

        String[] interfaces = new String[interfaceCount];

        for (int i = 0; i < interfaceCount; i++) {
            int cpInterfaceIndex = readUnsignedShort(codeIndex);
            codeIndex += 2;
            interfaces[i] = getTypeDescription(cpInterfaceIndex);
        }

        return interfaces;
    }

    private static class MemberInfo extends ObjectWithAttributes {
        @NonNegative
        public final int accessFlags;
        @NonNull
        public final String name;
        @NonNull
        public final String desc;
        @Nullable
        public String signature;

        MemberInfo(@NonNegative int accessFlags, @NonNull String name, @NonNull String desc,
                @NonNegative int attributeCount) {
            this.accessFlags = accessFlags;
            this.name = name;
            this.desc = desc;
        }

        public final boolean isStatic() {
            return (accessFlags & Access.STATIC) != 0;
        }

        public final boolean isAbstract() {
            return (accessFlags & Access.ABSTRACT) != 0;
        }

        public final boolean isSynthetic() {
            return (accessFlags & Access.SYNTHETIC) != 0;
        }
    }

    public static final class FieldInfo extends MemberInfo {
        FieldInfo(int accessFlags, @NonNull String name, @NonNull String desc, @NonNegative int attributeCount) {
            super(accessFlags, name, desc, attributeCount);
        }
    }

    @NonNull
    public List getFields() {
        int codeIndex = cpEndIndex + 6;
        int interfaceCount = readUnsignedShort(codeIndex);
        codeIndex += 2 + 2 * interfaceCount;

        int fieldCount = readUnsignedShort(codeIndex);
        codeIndex += 2;

        List fields;

        if (fieldCount == 0) {
            fields = Collections.emptyList();
        } else {
            fields = new ArrayList<>(fieldCount);

            for (int i = 0; i < fieldCount; i++) {
                int accessFlags = readUnsignedShort(codeIndex);
                codeIndex += 2;

                int cpNameIndex = readUnsignedShort(codeIndex);
                codeIndex += 2;
                String fieldName = getString(cpNameIndex);

                int cpDescIndex = readUnsignedShort(codeIndex);
                codeIndex += 2;
                String fieldDesc = getString(cpDescIndex);

                int attributeCount = readUnsignedShort(codeIndex);
                codeIndex += 2;

                FieldInfo fieldInfo = new FieldInfo(accessFlags, fieldName, fieldDesc, attributeCount);
                codeIndex = readAttributes(attributeCount, fieldInfo, codeIndex);
                fields.add(fieldInfo);
            }
        }

        fieldsEndIndex = codeIndex;
        return fields;
    }

    @NonNegative
    private int readAttributes(@NonNegative int attributeCount, @Nullable ObjectWithAttributes attributeOwner,
            @NonNegative int codeIndex) {
        EnumSet attributes = attributesToRead;
        boolean readAnnotations = false;

        if (attributes == null) {
            // noinspection AssignmentToMethodParameter
            attributeOwner = null;
        } else {
            readAnnotations = attributes.contains(Attribute.Annotations);
        }

        MethodInfo method = attributeOwner instanceof MethodInfo ? (MethodInfo) attributeOwner : null;

        for (int i = 0; i < attributeCount; i++) {
            int cpNameIndex = readUnsignedShort(codeIndex);
            codeIndex += 2;
            String attributeName = getString(cpNameIndex);

            int attributeLength = readInt(codeIndex);
            codeIndex += 4;

            if (attributeOwner != null) {
                if (method != null) {
                    method.readAttributes(attributeName, codeIndex);
                }

                if (readAnnotations && "RuntimeVisibleAnnotations".equals(attributeName)) {
                    attributeOwner.annotations = readAnnotations(codeIndex);
                }
            }

            codeIndex += attributeLength;
        }

        return codeIndex;
    }

    public static final class AnnotationInfo {
        @NonNull
        public final String name;

        AnnotationInfo(@NonNull String name) {
            this.name = name;
        }
    }

    @NonNull
    private List readAnnotations(@NonNegative int codeIndex) {
        int numAnnotations = readUnsignedShort(codeIndex);
        codeIndex += 2;

        List annotationInfos = new ArrayList<>(numAnnotations);

        for (int i = 0; i < numAnnotations; i++) {
            codeIndex = readAnnotation(annotationInfos, codeIndex);
        }

        return annotationInfos;
    }

    @NonNegative
    private int readAnnotation(@NonNull List currentAnnotations, @NonNegative int codeIndex) {
        int cpTypeIndex = readUnsignedShort(codeIndex);
        codeIndex += 2;

        String annotationTypeDesc = getString(cpTypeIndex);

        readUnsignedShort(codeIndex);
        codeIndex += 2;

        // for (int i = 0; i < numElementValuePairs; i++) {
        // int cpElementNameIndex = readUnsignedShort(codeIndex);
        // codeIndex += 2;
        //
        // int tag = code[codeIndex++];
        // // TODO: continue implementing
        // }

        AnnotationInfo annotation = new AnnotationInfo(annotationTypeDesc);
        currentAnnotations.add(annotation);

        return codeIndex;
    }

    @NonNegative
    private int getFieldsEndIndex() {
        int codeIndex = fieldsEndIndex;

        if (codeIndex == 0) {
            codeIndex = cpEndIndex + 6;
            int interfaceCount = readUnsignedShort(codeIndex);
            codeIndex += 2 + 2 * interfaceCount;

            int fieldCount = readUnsignedShort(codeIndex);
            codeIndex += 2;

            for (int i = 0; i < fieldCount; i++) {
                codeIndex += 6;

                int attributeCount = readUnsignedShort(codeIndex);
                codeIndex += 2;

                codeIndex = readAttributes(attributeCount, null, codeIndex);
            }

            fieldsEndIndex = codeIndex;
        }

        return codeIndex;
    }

    public final class MethodInfo extends MemberInfo {
        @Nullable
        public String[] parameters;

        MethodInfo(int accessFlags, @NonNull String name, @NonNull String desc, @NonNegative int attributeCount) {
            super(accessFlags, name, desc, attributeCount);
        }

        public boolean isMethod() {
            return name.charAt(0) != '<';
        }

        public boolean isConstructor() {
            return "".equals(name);
        }

        void readAttributes(@NonNull String attributeName, @NonNegative int codeIndex) {
            assert attributesToRead != null;

            if ("Code".equals(attributeName)) {
                if (attributesToRead.contains(Attribute.Parameters)) {
                    readParameters(codeIndex);
                }
            } else if ("Signature".equals(attributeName) && attributesToRead.contains(Attribute.Signature)) {
                readSignature(codeIndex);
            }
        }

        private void readParameters(@NonNegative int codeIndex) {
            codeIndex += 4;

            int codeLength = readInt(codeIndex);
            codeIndex += 4 + codeLength;

            int exceptionTableLength = readUnsignedShort(codeIndex);
            codeIndex += 2 + 8 * exceptionTableLength;

            int attributeCount = readUnsignedShort(codeIndex);
            codeIndex += 2;

            readParameters(attributeCount, codeIndex);
        }

        private void readParameters(@NonNegative int attributeCount, @NonNegative int codeIndex) {
            for (int i = 0; i < attributeCount; i++) {
                int cpNameIndex = readUnsignedShort(codeIndex);
                codeIndex += 2;
                String attributeName = getString(cpNameIndex);

                int attributeLength = readInt(codeIndex);
                codeIndex += 4;

                if ("LocalVariableTable".equals(attributeName)) {
                    parameters = readParametersFromLocalVariableTable(codeIndex);
                    break;
                }

                codeIndex += attributeLength;
            }
        }

        @Nullable
        private String[] readParametersFromLocalVariableTable(@NonNegative int codeIndex) {
            int localVariableTableLength = readUnsignedShort(codeIndex);
            codeIndex += 2;

            int arraySize = getSumOfArgumentSizes(desc);

            if (arraySize == 0) {
                return null;
            }

            if (!isStatic()) {
                arraySize++;
            }

            String[] parameterNames = new String[arraySize];

            for (int i = 0; i < localVariableTableLength; i++) {
                codeIndex += 4;

                int cpLocalVarNameIndex = readUnsignedShort(codeIndex);
                codeIndex += 2;
                String localVarName = getString(cpLocalVarNameIndex);

                if ("this".equals(localVarName)) {
                    codeIndex += 4;
                    continue;
                }

                codeIndex += 2;

                int localVarIndex = readUnsignedShort(codeIndex);
                codeIndex += 2;

                if (localVarIndex < arraySize) {
                    parameterNames[localVarIndex] = localVarName;
                }
            }

            return compactArray(parameterNames);
        }

        @NonNegative
        private int getSumOfArgumentSizes(@NonNull String memberDesc) {
            int sum = 0;
            int i = 1;

            while (true) {
                char c = memberDesc.charAt(i);
                i++;

                switch (c) {
                    case ')':
                        return sum;
                    case 'L':
                        while (memberDesc.charAt(i) != ';') {
                            i++;
                        }
                        i++;
                        sum++;
                        break;
                    case '[':
                        while ((c = memberDesc.charAt(i)) == '[') {
                            i++;
                        }
                        if (isDoubleSizeType(c)) { // if the array element type is double size...
                            i++;
                            sum++; // ...then count it here, otherwise let the outer loop count it
                        }
                        break;
                    default:
                        if (isDoubleSizeType(c)) {
                            sum += 2;
                        } else {
                            sum++;
                        }
                        break;
                }
            }
        }

        private boolean isDoubleSizeType(char typeCode) {
            return typeCode == 'D' || typeCode == 'J';
        }

        @Nullable
        private String[] compactArray(@NonNull String[] arrayPossiblyWithNulls) {
            int n = arrayPossiblyWithNulls.length;
            int j = n - 1;
            int i = 0;

            while (i < j) {
                if (arrayPossiblyWithNulls[i] == null) {
                    System.arraycopy(arrayPossiblyWithNulls, i + 1, arrayPossiblyWithNulls, i, j - i);
                    arrayPossiblyWithNulls[j] = null;
                    j--;
                } else {
                    i++;
                }
            }

            return n == 1 && arrayPossiblyWithNulls[0] == null ? null : arrayPossiblyWithNulls;
        }

        private void readSignature(@NonNegative int codeIndex) {
            int cpSignatureIndex = readUnsignedShort(codeIndex);
            signature = getString(cpSignatureIndex);
        }
    }

    @NonNull
    public List getMethods() {
        int codeIndex = getFieldsEndIndex();
        int methodCount = readUnsignedShort(codeIndex);
        codeIndex += 2;

        List methods = new ArrayList<>(methodCount);

        for (int i = 0; i < methodCount; i++) {
            int accessFlags = readUnsignedShort(codeIndex);
            codeIndex += 2;

            int cpNameIndex = readUnsignedShort(codeIndex);
            codeIndex += 2;
            String methodName = getString(cpNameIndex);

            int cpDescIndex = readUnsignedShort(codeIndex);
            codeIndex += 2;
            String methodDesc = getString(cpDescIndex);

            int attributeCount = readUnsignedShort(codeIndex);
            codeIndex += 2;

            MethodInfo methodInfo = new MethodInfo(accessFlags, methodName, methodDesc, attributeCount);
            codeIndex = readAttributes(attributeCount, methodInfo, codeIndex);
            methods.add(methodInfo);
        }

        methodsEndIndex = codeIndex;
        return methods;
    }

    @NonNegative
    private int getMethodsEndIndex() {
        int codeIndex = methodsEndIndex;

        if (codeIndex == 0) {
            codeIndex = getFieldsEndIndex();

            int methodCount = readUnsignedShort(codeIndex);
            codeIndex += 2;

            for (int i = 0; i < methodCount; i++) {
                codeIndex += 6;

                int attributeCount = readUnsignedShort(codeIndex);
                codeIndex += 2;

                codeIndex = readAttributes(attributeCount, null, codeIndex);
            }

            methodsEndIndex = codeIndex;
        }

        return codeIndex;
    }

    @NonNull
    public List getAnnotations() {
        if (annotations == null) {
            int codeIndex = getMethodsEndIndex();
            int attributeCount = readUnsignedShort(codeIndex);
            codeIndex += 2;

            readAttributes(attributeCount, this, codeIndex);

            if (annotations == null) {
                annotations = Collections.emptyList();
            }
        }

        return annotations;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy