Package org.jnode.vm.classmgr

Source Code of org.jnode.vm.classmgr.ClassDecoder

/*
* $Id$
*
* Copyright (C) 2003-2014 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.vm.classmgr;

import java.io.UTFDataFormatException;
import java.lang.annotation.Annotation;
import java.nio.ByteBuffer;
import java.security.ProtectionDomain;
import org.jnode.annotation.AllowedPackages;
import org.jnode.annotation.CheckPermission;
import org.jnode.annotation.DoPrivileged;
import org.jnode.annotation.Inline;
import org.jnode.annotation.KernelSpace;
import org.jnode.annotation.LoadStatics;
import org.jnode.annotation.MagicPermission;
import org.jnode.annotation.NoFieldAlignments;
import org.jnode.annotation.NoInline;
import org.jnode.annotation.NoReadBarrier;
import org.jnode.annotation.NoWriteBarrier;
import org.jnode.annotation.PrivilegedActionPragma;
import org.jnode.annotation.SharedStatics;
import org.jnode.annotation.Uninterruptible;
import org.jnode.bootlog.BootLogInstance;
import org.jnode.vm.JvmType;
import org.jnode.vm.facade.VmUtils;
import org.vmmagic.pragma.PragmaException;
import org.vmmagic.pragma.UninterruptiblePragma;
import sun.reflect.annotation.AnnotationParser;
import sun.reflect.annotation.ExceptionProxy;

/**
* Decoder of .class files into VmType instances.
*
* @author Ewout Prangsma (epr@users.sourceforge.net)
*/
public final class ClassDecoder {

    // ------------------------------------------
    // VM ClassLoader Code
    // ------------------------------------------

    private static char[] SourceFileAttrName;

    private static char[] SignatureAttrName;

    private static char[] CodeAttrName;

    private static char[] ConstantValueAttrName;

    private static char[] ExceptionsAttrName;

    private static char[] LocalVariableTableAttrName;

    private static char[] LineNrTableAttrName;

    private static char[] RuntimeVisibleAnnotationsAttrName;

    private static char[] RuntimeInvisibleAnnotationsAttrName;

    private static char[] RuntimeVisibleParameterAnnotationsAttrName;

    private static char[] RuntimeInvisibleParameterAnnotationsAttrName;

    private static char[] AnnotationDefaultAttrName;

    @SuppressWarnings("deprecation")
    private static final MethodPragmaException[] METHOD_PRAGMA_EXCEPTIONS = new MethodPragmaException[]{
        new MethodPragmaException(UninterruptiblePragma.class,
            MethodPragmaFlags.UNINTERRUPTIBLE),
        new MethodPragmaException(org.vmmagic.pragma.InlinePragma.class,
            MethodPragmaFlags.INLINE),
        new MethodPragmaException(org.vmmagic.pragma.NoInlinePragma.class,
            MethodPragmaFlags.NOINLINE)};

    private static final PragmaInterface[] INTERFACE_PRAGMAS = new PragmaInterface[]{new PragmaInterface(
        org.vmmagic.pragma.Uninterruptible.class,
        TypePragmaFlags.UNINTERRUPTIBLE)};

    private static final PragmaAnnotation[] CLASS_ANNOTATIONS = new PragmaAnnotation[]{
        new PragmaAnnotation(MagicPermission.class,
            TypePragmaFlags.MAGIC_PERMISSION),
        new PragmaAnnotation(NoFieldAlignments.class,
            TypePragmaFlags.NO_FIELD_ALIGNMENT),
        new PragmaAnnotation(SharedStatics.class,
            TypePragmaFlags.SHAREDSTATICS),
        new PragmaAnnotation(Uninterruptible.class,
            TypePragmaFlags.UNINTERRUPTIBLE)};

    private static final PragmaAnnotation[] METHOD_ANNOTATIONS = new PragmaAnnotation[]{
        new PragmaAnnotation(CheckPermission.class,
            MethodPragmaFlags.CHECKPERMISSION),
        new PragmaAnnotation(DoPrivileged.class,
            MethodPragmaFlags.DOPRIVILEGED),
        new PragmaAnnotation(Inline.class, MethodPragmaFlags.INLINE),
        new PragmaAnnotation(LoadStatics.class,
            MethodPragmaFlags.LOADSTATICS),
        new PragmaAnnotation(NoInline.class, MethodPragmaFlags.NOINLINE),
        new PragmaAnnotation(NoReadBarrier.class,
            MethodPragmaFlags.NOREADBARRIER),
        new PragmaAnnotation(NoWriteBarrier.class,
            MethodPragmaFlags.NOWRITEBARRIER),
        new PragmaAnnotation(PrivilegedActionPragma.class,
            MethodPragmaFlags.PRIVILEGEDACTION),
        new PragmaAnnotation(Uninterruptible.class,
            MethodPragmaFlags.UNINTERRUPTIBLE),
        new PragmaAnnotation(KernelSpace.class,
            MethodPragmaFlags.KERNELSPACE)};

    /**
     * Names of classes that you use shared statics, but cannot be modified.
     */
    private static final String[] SHARED_STATICS_CLASSNAMES = {
        "java.util.TreeMap",
        "org.apache.log4j.LogManager",
    };

    private static final byte[] TYPE_SIZES = {1, 2, 4, 8};

    private static final Class<?>[] BOOT_TYPES = new Class[]{Class.class,
        String.class, Integer.class, Long.class};

    /**
     * Align the given value on the given alignment.
     *
     * @param value
     * @param alignment
     * @return the new value
     */
    private static final int align(int value, int alignment) {
        while ((value % alignment) != 0) {
            value++;
        }
        return value;
    }

    /**
     * Is the given type of the BOOT_TYPES classes.
     *
     * @param type
     */
    private static final boolean isBootType(VmType<?> type) {
        final String typeName = type.getName();
        for (Class<?> c : BOOT_TYPES) {
            if (c.getName().equals(typeName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Align the offsets of the fields in the class optimized for minimal object
     * size.
     *
     * @param fields
     * @return The objectsize taken by all the fields
     */
    private static final int alignInstanceFields(VmField[] fields, int slotSize) {
        int objectSize = 0;
        for (byte currentTypeSize : TYPE_SIZES) {
            boolean aligned = false;
            for (VmField f : fields) {
                if (!f.isStatic() && (f.getTypeSize() == currentTypeSize)) {
                    if (!aligned) {
                        // Align on the current type size
                        objectSize = align(objectSize, Math.min(
                            currentTypeSize, slotSize));
                        aligned = true;
                    }
                    final VmInstanceField fld = (VmInstanceField) f;
                    fld.setOffset(objectSize);
                    objectSize += currentTypeSize;
                }
            }
        }
        // Make sure the object size is 32-bit aligned
        return align(objectSize, 4);
    }

    private static final void cl_init() {
        if (ConstantValueAttrName == null) {
            ConstantValueAttrName = "ConstantValue".toCharArray();
            CodeAttrName = "Code".toCharArray();
            SourceFileAttrName = "SourceFile".toCharArray();
            SignatureAttrName = "Signature".toCharArray();
            ExceptionsAttrName = "Exceptions".toCharArray();
            LineNrTableAttrName = "LineNumberTable".toCharArray();
            LocalVariableTableAttrName = "LocalVariableTable".toCharArray();
            RuntimeVisibleAnnotationsAttrName = "RuntimeVisibleAnnotations"
                .toCharArray();
            RuntimeInvisibleAnnotationsAttrName = "RuntimeInvisibleAnnotations"
                .toCharArray();
            RuntimeVisibleParameterAnnotationsAttrName = "RuntimeVisibleParameterAnnotations"
                .toCharArray();
            RuntimeInvisibleParameterAnnotationsAttrName = "RuntimeInvisibleParameterAnnotations"
                .toCharArray();
            AnnotationDefaultAttrName = "AnnotationDefault".toCharArray();
        }
    }

    /**
     * Decode a given class.
     *
     * @param data
     * @param rejectNatives
     * @param clc
     * @param protectionDomain
     * @return The decoded class
     * @throws ClassFormatError
     */
    private static final VmType decodeClass(ByteBuffer data,
                                            boolean rejectNatives, VmClassLoader clc,
                                            ProtectionDomain protectionDomain) throws ClassFormatError {
        final VmSharedStatics sharedStatics = clc.getSharedStatics();
        final VmIsolatedStatics isolatedStatics = clc.getIsolatedStatics();
        final int slotSize = clc.getArchitecture().getReferenceSize();

        final int magic = data.getInt();
        if (magic != 0xCAFEBABE) {
            throw new ClassFormatError("invalid magic");
        }
        final int min_version = data.getChar();
        final int maj_version = data.getChar();

        if (false) {
            BootLogInstance.get().debug("Class file version " + maj_version + ';'
                + min_version);
        }

        final int cpcount = data.getChar();
        // allocate enough space for the CP
        final byte[] tags = new byte[cpcount];
        final VmCP cp = new VmCP(cpcount);
        for (int i = 1; i < cpcount; i++) {
            final int tag = data.get() & 0xFF;
            tags[i] = (byte) tag;
            switch (tag) {
                case 1:
                    // Utf8
                    cp.setUTF8(i, readUTF(data));
                    break;
                case 3:
                    // int
                    cp.setInt(i, data.getInt());
                    break;
                case 4:
                    // float
                    // cp.setInt(i, data.getInt());
                    final int ival = data.getInt();
                    final float fval = Float.intBitsToFloat(ival);
                    cp.setFloat(i, fval);
                    break;
                case 5:
                    // long
                    cp.setLong(i, data.getLong());
                    i++;
                    break;
                case 6:
                    // double
                    // cp.setLong(i, data.getLong());
                    final long lval = data.getLong();
                    final double dval = Double.longBitsToDouble(lval);
                    cp.setDouble(i, dval);
                    i++;
                    break;
                case 7:
                    // class
                    cp.setInt(i, data.getChar());
                    break;
                case 8:
                    // String
                    cp.setInt(i, data.getChar());
                    break;
                case 9: // Fieldref
                case 10: // Methodref
                case 11: // IMethodref
                {
                    final int clsIdx = data.getChar();
                    final int ntIdx = data.getChar();
                    cp.setInt(i, clsIdx << 16 | ntIdx);
                    break;
                }
                case 12:
                    // Name and Type
                {
                    final int nIdx = data.getChar();
                    final int dIdx = data.getChar();
                    cp.setInt(i, nIdx << 16 | dIdx);
                    break;
                }
                default:
                    throw new ClassFormatError("Invalid constantpool tag: "
                        + tags[i]);
            }
        }

        // Now patch the required entries (level 1)
        for (int i = 1; i < cpcount; i++) {
            switch (tags[i]) {
                case 7: {
                    // Class
                    final int idx = cp.getInt(i);
                    final VmConstClass constClass = new VmConstClass(cp
                        .getUTF8(idx));
                    cp.setConstClass(i, constClass);
                    break;
                }
                case 8: {
                    // String
                    final int idx = cp.getInt(i);
                    final int staticsIdx = sharedStatics
                        .allocConstantStringField(cp.getUTF8(idx));
                    final VmConstString constStr = new VmConstString(staticsIdx);
                    cp.setString(i, constStr);
                    break;
                }
            }
        }

        // Now patch the required entries (level 2)
        for (int i = 1; i < cpcount; i++) {
            final int tag = tags[i];
            if ((tag >= 9) && (tag <= 11)) {
                final int v = cp.getInt(i);
                final VmConstClass constClass = cp.getConstClass(v >>> 16);
                final int nat = cp.getInt(v & 0xFFFF);
                final String name = cp.getUTF8(nat >>> 16);
                final String descriptor = cp.getUTF8(nat & 0xFFFF);
                switch (tag) {
                    case 9:
                        // FieldRef
                        cp.setConstFieldRef(i, new VmConstFieldRef(constClass,
                            name, descriptor));
                        break;
                    case 10:
                        // MethodRef
                        cp.setConstMethodRef(i, new VmConstMethodRef(constClass,
                            name, descriptor));
                        break;
                    case 11:
                        // IMethodRef
                        cp.setConstIMethodRef(i, new VmConstIMethodRef(constClass,
                            name, descriptor));
                        break;
                }
            }
        }

        // Cleanup the unwantend entries
        for (int i = 1; i < cpcount; i++) {
            switch (tags[i]) {
                case 12:
                    // Name and Type
                    cp.reset(i);
                    break;
            }
        }

        final int classModifiers = data.getChar();

        final VmConstClass this_class = cp.getConstClass(data.getChar());
        final String clsName = this_class.getClassName();

        final VmConstClass super_class = cp.getConstClass(data.getChar());
        final String superClassName;
        if (super_class != null) {
            superClassName = super_class.getClassName();
        } else {
            superClassName = null;
        }

        // Allocate the class object
        final VmType cls;
        if (Modifier.isInterface(classModifiers)) {
            cls = new VmInterfaceClass(clsName, superClassName, clc,
                classModifiers, protectionDomain);
        } else {
            cls = new VmNormalClass(clsName, superClassName, clc,
                classModifiers, protectionDomain);
        }
        cls.setCp(cp);

        // Determine if we can safely align the fields
        //int pragmaFlags = 0;
        if (isBootType(cls)) {
            cls.addPragmaFlags(TypePragmaFlags.NO_FIELD_ALIGNMENT);
        }
        cls.addPragmaFlags(getClassNamePragmaFlags(clsName));

        // Interface table
        cls.addPragmaFlags(readInterfaces(data, cls, cp));

        // Field table
        final FieldData[] fieldData = readFields(data, cp, slotSize, clc);

        // Method Table
        readMethods(data, rejectNatives, cls, cp, sharedStatics, clc);

        // Read class attributes
        final int acount = data.getChar();
        VmAnnotation[] rVisAnn = null;
        VmAnnotation[] rInvisAnn = null;
        String sourceFile = null;
        String signature = null;
        for (int a = 0; a < acount; a++) {
            final String attrName = cp.getUTF8(data.getChar());
            final int length = data.getInt();
            if (VmArray.equals(RuntimeVisibleAnnotationsAttrName, attrName)) {
                byte[] buf = new byte[length];
                data.slice().get(buf);
                cls.setRawAnnotations(buf);
                rVisAnn = readRuntimeAnnotations(data, cp, true, clc);
            } else if (VmArray.equals(RuntimeInvisibleAnnotationsAttrName,
                attrName)) {
                rInvisAnn = readRuntimeAnnotations(data, cp, false, clc);
            } else if (VmArray.equals(SourceFileAttrName, attrName)) {
                sourceFile = cp.getUTF8(data.getChar());
            } else if (VmArray.equals(SignatureAttrName, attrName)) {
                signature = cp.getUTF8(data.getChar());
            } else {
                skip(data, length);
            }
        }
        cls.setRuntimeAnnotations(rVisAnn);
        cls.setSourceFile(sourceFile);
        cls.setSignature(signature);
        if (rInvisAnn != null) {
            cls.addPragmaFlags(getClassPragmaFlags(rInvisAnn, clsName));
        }
        if (rVisAnn != null) {
            cls.addPragmaFlags(getClassPragmaFlags(rVisAnn, clsName));
        }

        // Create the fields
        if (fieldData != null) {
            createFields(cls, fieldData, sharedStatics, isolatedStatics,
                slotSize, cls.getPragmaFlags());
        }

        return cls;
    }

    /**
     * Convert a class-file image into a Class object. Steps taken in this
     * phase: 1. Decode the class-file image (CLS_LS_DECODED) 2. Load the
     * super-class of the loaded class (CLS_LS_DEFINED) 3. Link the class so
     * that the VMT is set and the offset of the non-static fields are set
     * correctly.
     *
     * @param className
     * @param data
     * @param rejectNatives
     * @param clc
     * @param protectionDomain
     * @return The defined class
     */
    public static final VmType defineClass(String className, ByteBuffer data,
                                           boolean rejectNatives, VmClassLoader clc,
                                           ProtectionDomain protectionDomain) {
        cl_init();
        return decodeClass(data, rejectNatives, clc, protectionDomain);
    }

    /**
     * Gets the bytecode of a native code replacement method.
     *
     * @param method
     * @param cl
     * @return the bytecode
     */
    private static VmByteCode getNativeCodeReplacement(VmMethod method,
                                                       VmClassLoader cl, boolean verbose) {
        final String className = method.getDeclaringClass().getName();
        final String nativeClassName = VmUtils.getNativeClassName(className);
        final VmType nativeType;
        try {
            nativeType = cl.loadClass(nativeClassName, false);
        } catch (ClassNotFoundException ex) {
            if (verbose) {
                BootLogInstance.get().error("Native class replacement (" + nativeClassName
                    + ") not found");
            }
            return null;
        }

        String signature = method.getSignature();
        if (!method.isStatic()) {
            signature = '(' + Signature.toSignature(method.getDeclaringClass()) + signature.substring(1);
        }

        final VmMethod nativeMethod = nativeType.getNativeMethodReplacement(method.getName(), signature);

        if (nativeMethod == null) {
            if (verbose) {
                BootLogInstance.get().error("Native method replacement (" + method
                    + ") not found");
            }
            return null;
        }
        if (!nativeMethod.isStatic()) {
            throw new ClassFormatError(
                "Native method replacement must be static");
        }
        return nativeMethod.getBytecode();
    }

    /**
     * Decode the data of a code-attribute
     *
     * @param data
     * @param cls
     * @param cp
     * @param method
     * @return The read code
     */
    private static final VmByteCode readCode(ByteBuffer data, VmType cls,
                                             VmCP cp, VmMethod method) {

        final int maxStack = data.getChar();
        final int noLocals = data.getChar();
        final int codelength = data.getInt();
        final ByteBuffer code = readBytes(data, codelength);

        // Read the exception Table
        final int ecount = data.getChar();
        final VmInterpretedExceptionHandler[] etable = new VmInterpretedExceptionHandler[ecount];
        for (int i = 0; i < ecount; i++) {
            final int startPC = data.getChar();
            final int endPC = data.getChar();
            final int handlerPC = data.getChar();
            final int catchType = data.getChar();
            etable[i] = new VmInterpretedExceptionHandler(cp, startPC, endPC,
                handlerPC, catchType);
        }

        // Read the attributes
        VmLineNumberMap lnTable = null;
        VmLocalVariableTable lvTable = VmLocalVariableTable.EMPTY;
        final int acount = data.getChar();
        for (int i = 0; i < acount; i++) {
            final String attrName = cp.getUTF8(data.getChar());
            final int len = data.getInt();
            if (VmArray.equals(LineNrTableAttrName, attrName)) {
                lnTable = readLineNrTable(data);
            } else if (VmArray.equals(LocalVariableTableAttrName, attrName)) {
                lvTable = readLocalVariableTable(data, cp);
            } else {
                skip(data, len);
            }
        }

        return new VmByteCode(method, code, noLocals, maxStack, etable,
            lnTable, lvTable);
    }

    /**
     * Decode the data of a Exceptions attribute
     *
     * @param data
     * @param cls
     * @param cp
     * @return The read exceptions
     */
    private static final VmExceptions readExceptions(ByteBuffer data,
                                                     VmType cls, VmCP cp) {

        // Read the exceptions
        char pragmaFlags = 0;
        int pragmas = 0;
        final int ecount = data.getChar();
        final VmConstClass[] list = new VmConstClass[ecount];
        for (int i = 0; i < ecount; i++) {
            final int idx = data.getChar();
            final VmConstClass ccls = cp.getConstClass(idx);
            list[i] = ccls;
            for (MethodPragmaException mp : METHOD_PRAGMA_EXCEPTIONS) {
                if (ccls.getClassName().equals(mp.className)) {
                    pragmaFlags |= mp.flags;
                    pragmas++;
                    list[i] = null;
                    break;
                }
            }
        }
        if (pragmas > 0) {
            final int newCnt = ecount - pragmas;
            if (newCnt == 0) {
                return new VmExceptions(null, pragmaFlags);
            } else {
                final VmConstClass[] newList = new VmConstClass[newCnt];
                int k = 0;
                for (int i = 0; i < ecount; i++) {
                    final VmConstClass ccls = list[i];
                    if (ccls != null) {
                        newList[k++] = ccls;
                    }
                }
                return new VmExceptions(newList, pragmaFlags);
            }
        } else {
            return new VmExceptions(list, pragmaFlags);
        }
    }

    /**
     * Read the fields table
     *
     * @param data
     * @param cp
     * @param slotSize
     * @param loader
     */
    private static FieldData[] readFields(ByteBuffer data, VmCP cp, int slotSize, VmClassLoader loader) {
        final int fcount = data.getChar();
        if (fcount > 0) {
            final FieldData[] ftable = new FieldData[fcount];

            for (int i = 0; i < fcount; i++) {
                int modifiers = data.getChar();
                final String name = cp.getUTF8(data.getChar());
                final String signature = cp.getUTF8(data.getChar());
                final boolean isstatic = ((modifiers & Modifier.ACC_STATIC) != 0);

                // Read field attributes
                final int acount = data.getChar();
                VmAnnotation[] rVisAnn = null;
                byte[] rawAnnotations = null;
                Object constantValue = null;
                for (int a = 0; a < acount; a++) {
                    final String attrName = cp.getUTF8(data.getChar());
                    final int length = data.getInt();
                    if (isstatic
                        && VmArray.equals(ConstantValueAttrName, attrName)) {
                        constantValue = cp.getAny(data.getChar());
                    } else if (VmArray.equals(
                        RuntimeVisibleAnnotationsAttrName, attrName)) {
                        rawAnnotations = new byte[length];
                        data.slice().get(rawAnnotations);
                        rVisAnn = readRuntimeAnnotations(data, cp, true, loader);
                    } else if (VmArray.equals(
                        RuntimeInvisibleAnnotationsAttrName, attrName)) {
                        readRuntimeAnnotations(data, cp, false, loader);
                    } else {
                        skip(data, length);
                    }
                }

                ftable[i] = new FieldData(name, signature, modifiers, constantValue, rVisAnn, rawAnnotations);
            }
            return ftable;
        } else {
            return null;
        }
    }

    /**
     * Read the fields table
     *
     * @param cls
     * @param fieldDatas
     * @param sharedStatics
     * @param isolatedStatics
     * @param slotSize
     * @param pragmaFlags
     */
    private static void createFields(VmType<?> cls, FieldData[] fieldDatas,
                                     VmSharedStatics sharedStatics, VmIsolatedStatics isolatedStatics,
                                     int slotSize, int pragmaFlags) {
        final int fcount = fieldDatas.length;
        final VmField[] ftable = new VmField[fcount];

        int objectSize = 0;
        for (int i = 0; i < fcount; i++) {
            final FieldData fd = fieldDatas[i];
            final boolean wide;
            int modifiers = fd.modifiers;
            final String name = fd.name;
            final String signature = fd.signature;
            switch (signature.charAt(0)) {
                case 'J':
                case 'D':
                    modifiers = modifiers | Modifier.ACC_WIDE;
                    wide = true;
                    break;
                default:
                    wide = false;
            }
            final boolean isstatic = (modifiers & Modifier.ACC_STATIC) != 0;
            final int staticsIdx;
            final VmField fs;
            final VmStatics statics;
            if (isstatic) {
                // Determine if the static field should be shared.
                final boolean shared = cls.isSharedStatics();
                if (shared) {
                    statics = sharedStatics;
                } else {
                    statics = isolatedStatics;
                }

                // If static allocate space for it.
                switch (signature.charAt(0)) {
                    case 'B':
                        staticsIdx = statics.allocIntField();
                        break;
                    case 'C':
                        staticsIdx = statics.allocIntField();
                        break;
                    case 'D':
                        staticsIdx = statics.allocLongField();
                        break;
                    case 'F':
                        staticsIdx = statics.allocIntField();
                        break;
                    case 'I':
                        staticsIdx = statics.allocIntField();
                        break;
                    case 'J':
                        staticsIdx = statics.allocLongField();
                        break;
                    case 'S':
                        staticsIdx = statics.allocIntField();
                        break;
                    case 'Z':
                        staticsIdx = statics.allocIntField();
                        break;
                    default: {
                        if (Modifier.isAddressType(signature)) {
                            staticsIdx = statics.allocAddressField();
                        } else {
                            staticsIdx = statics.allocObjectField();
                            // System.out.println(NumberUtils.hex(staticsIdx)
                            // + "\t" + cls.getName() + "." + name);
                        }
                        break;
                    }
                }
                fs = new VmStaticField(name, signature, modifiers, staticsIdx,
                    cls, slotSize, shared);
            } else {
                staticsIdx = -1;
                statics = null;
                final int fieldOffset;
                // Set the offset (keep in mind that this will be fixed
                // by ClassResolver with respect to the objectsize of the
                // super-class.
                fieldOffset = objectSize;
                // Increment the objectSize
                if (wide)
                    objectSize += 8;
                else if (Modifier.isPrimitive(signature)) {
                    objectSize += 4;
                } else {
                    objectSize += slotSize;
                }
                fs = new VmInstanceField(name, signature, modifiers,
                    fieldOffset, cls, slotSize);
            }
            ftable[i] = fs;

            // Read field attributes           
            if (isstatic && (fd.constantValue != null)) {
                switch (signature.charAt(0)) {
                    case 'B':
                    case 'C':
                    case 'I':
                    case 'S':
                    case 'Z':
                        statics.setInt(staticsIdx, ((VmConstInt) fd.constantValue).intValue());
                        break;
                    case 'D':
                        final long lval = Double.doubleToRawLongBits(((VmConstDouble) fd.constantValue).doubleValue());
                        statics.setLong(staticsIdx, lval);
                        break;
                    case 'F':
                        final int ival = Float.floatToRawIntBits(((VmConstFloat) fd.constantValue) .floatValue());
                        statics.setInt(staticsIdx, ival);
                        break;
                    case 'J':
                        statics.setLong(staticsIdx, ((VmConstLong) fd.constantValue).longValue());
                        break;
                    default:
                        // throw new IllegalArgumentException("signature "
                        // + signature);
                        statics.setObject(staticsIdx, fd.constantValue);
                        break;
                }
            }
            fs.setRuntimeAnnotations(fd.rVisAnn);
            fs.setRawAnnotations(fd.rawAnnotations);
        }

        // Align the instance fields for minimal object size.
        if ((pragmaFlags & TypePragmaFlags.NO_FIELD_ALIGNMENT) == 0) {
            objectSize = alignInstanceFields(ftable, slotSize);
        }

        cls.setFieldTable(ftable);
        if (objectSize > 0) {
            ((VmNormalClass<?>) cls).setObjectSize(objectSize);
        }
    }

    /**
     * Read the interfaces table
     *
     * @param data
     * @param cls
     * @param cp
     * @return Some flags
     */
    private static int readInterfaces(ByteBuffer data, VmType cls, VmCP cp) {
        int flags = 0;
        final int icount = data.getChar();
        if (icount > 0) {
            final VmImplementedInterface[] itable = new VmImplementedInterface[icount];
            for (int i = 0; i < icount; i++) {
                final VmConstClass icls = cp.getConstClass(data.getChar());
                final String iclsName = icls.getClassName();
                itable[i] = new VmImplementedInterface(iclsName);

                for (PragmaInterface pi : INTERFACE_PRAGMAS) {
                    if (iclsName.equals(pi.className)) {
                        flags |= pi.flags;
                    }
                }
            }
            cls.setInterfaceTable(itable);
        }
        return flags;
    }

    /**
     * Decode the data of a LineNumberTable-attribute
     *
     * @param data
     * @return The line number map
     */
    private static final VmLineNumberMap readLineNrTable(ByteBuffer data) {
        final int len = data.getChar();
        final char[] lnTable = new char[len * VmLineNumberMap.LNT_ELEMSIZE];

        for (int i = 0; i < len; i++) {
            final int ofs = i * VmLineNumberMap.LNT_ELEMSIZE;
            lnTable[ofs + VmLineNumberMap.LNT_STARTPC_OFS] = data.getChar();
            lnTable[ofs + VmLineNumberMap.LNT_LINENR_OFS] = data.getChar();
        }

        return new VmLineNumberMap(lnTable);
    }

    /**
     * Decode the data of a LocalVariable-attribute
     *
     * @param data
     * @param cp
     * @return The line number map
     */
    private static final VmLocalVariableTable readLocalVariableTable(
        ByteBuffer data, VmCP cp) {
        final int len = data.getChar();
        if (len == 0) {
            return VmLocalVariableTable.EMPTY;
        } else {
            final VmLocalVariable[] table = new VmLocalVariable[len];

            for (int i = 0; i < len; i++) {
                final char startPc = data.getChar();
                final char length = data.getChar();
                final char nameIdx = data.getChar();
                final char descrIdx = data.getChar();
                final char index = data.getChar();

                table[i] = new VmLocalVariable(startPc, length, nameIdx,
                    descrIdx, index);
            }

            return new VmLocalVariableTable(table);
        }
    }

    /**
     * Read the method table
     *
     * @param data
     * @param rejectNatives
     * @param cls
     * @param cp
     * @param statics
     * @param cl
     */
    private static void readMethods(ByteBuffer data, boolean rejectNatives,
                                    VmType cls, VmCP cp, VmStatics statics, VmClassLoader cl) {
        final int mcount = data.getChar();
        if (mcount > 0) {
            final VmMethod[] mtable = new VmMethod[mcount];

            for (int i = 0; i < mcount; i++) {
                final int modifiers = data.getChar();
                final String name = cp.getUTF8(data.getChar());
                final String signature = cp.getUTF8(data.getChar());
                final boolean isStatic = ((modifiers & Modifier.ACC_STATIC) != 0);

                final VmMethod mts;
                final boolean isSpecial = name.equals("<init>");
                // final int staticsIdx = statics.allocMethod();
                if (isStatic || isSpecial) {
                    if (isSpecial) {
                        mts = new VmSpecialMethod(name, signature, modifiers,
                            cls);
                    } else {
                        mts = new VmStaticMethod(name, signature, modifiers,
                            cls);
                    }
                } else {
                    mts = new VmInstanceMethod(name, signature, modifiers, cls);
                }
                // statics.setMethod(staticsIdx, mts);
                mtable[i] = mts;

                // Read methods attributes
                final int acount = data.getChar();
                VmAnnotation[] rVisAnn = null;
                VmAnnotation[] rInvisAnn = null;
                for (int a = 0; a < acount; a++) {
                    String attrName = cp.getUTF8(data.getChar());
                    int length = data.getInt();
                    if (VmArray.equals(CodeAttrName, attrName)) {
                        mts.setBytecode(readCode(data, cls, cp, mts));
                    } else if (VmArray.equals(ExceptionsAttrName, attrName)) {
                        mts.setExceptions(readExceptions(data, cls, cp));
                    } else if (VmArray.equals(RuntimeVisibleAnnotationsAttrName, attrName)) {
                        byte[] buf = new byte[length];
                        data.slice().get(buf);
                        mts.setRawAnnotations(buf);

                        //todo will get obsolate with openjdk based annotation support
                        //rVisAnn = readRuntimeAnnotations(data, cp, true, cl);
                        rVisAnn = readRuntimeAnnotations2(data, cp, true, cl, cls);

                    } else if (VmArray.equals(RuntimeInvisibleAnnotationsAttrName, attrName)) {
                        rInvisAnn = readRuntimeAnnotations(data, cp, false, cl);
                    } else if (VmArray.equals(RuntimeVisibleParameterAnnotationsAttrName, attrName)) {

                        byte[] buf = new byte[length];
                        data.slice().get(buf);
                        mts.setRawParameterAnnotations(buf);
                        //todo will get obsolate with openjdk based annotation support
                        readRuntimeParameterAnnotations(data, cp, true, cl);
                    } else if (VmArray.equals(RuntimeInvisibleParameterAnnotationsAttrName, attrName)) {
                        readRuntimeParameterAnnotations(data, cp, false, cl);
                    } else if (VmArray.equals(AnnotationDefaultAttrName, attrName)) {
                        //todo will get obsolate with openjdk based annotation support
                        byte[] buf = new byte[length];
                        data.slice().get(buf);
                        mts.setRawAnnotationDefault(buf);

                        Class r_class;
                        VmType vtm = mts.getReturnType();
                        if (vtm.isPrimitive()) {
                            r_class = getClassForJvmType(vtm.getJvmType());
                        } else {
                            try {
                                r_class = Class.forName(vtm.getName(), false, vtm.getLoader().asClassLoader());
                            } catch (ClassNotFoundException cnf) {
                                throw new RuntimeException(cnf);
                            }
                        }
                        Object defo = AnnotationParser.parseMemberValue(r_class, data, new VmConstantPool(cls),
                            VmUtils.isRunningVm() ? cls.asClass() : cls.asClassDuringBootstrap());
                        mts.setAnnotationDefault(defo);
                    } else {
                        skip(data, length);
                    }
                }
                mts.setRuntimeAnnotations(rVisAnn);
                if (rVisAnn != null) {
                    mts.addPragmaFlags(getMethodPragmaFlags(rVisAnn, cls
                        .getName()));
                }
                if (rInvisAnn != null) {
                    mts.addPragmaFlags(getMethodPragmaFlags(rInvisAnn, cls
                        .getName()));
                }
                if ((modifiers & Modifier.ACC_NATIVE) != 0) {
                    final VmByteCode bc = getNativeCodeReplacement(mts, cl,
                        rejectNatives);
                    if (bc != null) {
                        mts.setModifier(false, Modifier.ACC_NATIVE);
                        mts.setBytecode(bc);
                    } else {
                        if (rejectNatives) {
                            throw new ClassFormatError("Native method " + mts);
                        }
                    }
                }
            }
            cls.setMethodTable(mtable);
        }
    }

    private static Class getClassForJvmType(int type) {
        switch (type) {
            case JvmType.BOOLEAN:
                return boolean.class;
            case JvmType.BYTE:
                return byte.class;
            case JvmType.SHORT:
                return short.class;
            case JvmType.CHAR:
                return char.class;
            case JvmType.INT:
                return int.class;
            case JvmType.FLOAT:
                return float.class;
            case JvmType.LONG:
                return long.class;
            case JvmType.DOUBLE:
                return double.class;
            case JvmType.VOID:
                return void.class;
            default:
                throw new IllegalArgumentException("Invalid JVM type: " + type);
        }
    }

    /**
     * Read a runtime parameter annotations attributes.
     *
     * @param data
     * @param cp
     */
    private static VmAnnotation[][] readRuntimeParameterAnnotations(
        ByteBuffer data, VmCP cp, boolean visible, VmClassLoader loader) {
        final int numParams = data.get();
        final VmAnnotation[][] arr = new VmAnnotation[numParams][];
        for (int i = 0; i < numParams; i++) {
            arr[i] = readRuntimeAnnotations(data, cp, visible, loader);
        }
        return arr;
    }

    /**
     * Read a runtime annotations attributes.
     *
     * @param data
     * @param cp
     */
    private static VmAnnotation[] readRuntimeAnnotations(ByteBuffer data,
                                                         VmCP cp, boolean visible, VmClassLoader loader) {
        final int numAnn = data.getChar();
        final VmAnnotation[] arr = new VmAnnotation[numAnn];
        for (int i = 0; i < numAnn; i++) {
            arr[i] = readAnnotation(data, cp, visible);
        }
        return arr;
    }

    /**
     * Read a runtime annotations attributes.
     *
     * @param data
     * @param cp
     */
    private static VmAnnotation[] readRuntimeAnnotations2(ByteBuffer data,
                                                          VmCP cp, boolean visible, VmClassLoader loader,
                                                          VmType vmtype) {
        final int numAnn = data.getChar();
        final VmAnnotation[] arr = new VmAnnotation[numAnn];
        for (int i = 0; i < numAnn; i++) {
            arr[i] = readAnnotation2(data, cp, visible, loader, vmtype);
        }
        return arr;
    }

    /**
     * Combine the pragma flags for a given list of annotations.
     *
     * @param annotations
     * @param className
     */
    private static int getMethodPragmaFlags(VmAnnotation[] annotations,
                                            String className) {
        int flags = 0;
        for (VmAnnotation a : annotations) {
            final String typeDescr = a.getTypeDescriptor();
            for (PragmaAnnotation ma : METHOD_ANNOTATIONS) {
                if (ma.typeDescr.equals(typeDescr)) {
                    ma.checkPragmaAllowed(className);
                    flags |= ma.flags;
                }
            }
        }
        return flags;
    }

    /**
     * Combine the pragma flags for a given list of annotations.
     *
     * @param annotations
     * @param className
     */
    private static int getClassPragmaFlags(VmAnnotation[] annotations,
                                           String className) {
        int flags = 0;
        for (VmAnnotation a : annotations) {
            final String typeDescr = a.getTypeDescriptor();
            for (PragmaAnnotation ma : CLASS_ANNOTATIONS) {
                if (ma.typeDescr.equals(typeDescr)) {
                    ma.checkPragmaAllowed(className);
                    flags |= ma.flags;
                }
            }
        }
        for (String name : SHARED_STATICS_CLASSNAMES) {
            if (className.equals(name)) {
                System.out.println("FOUND IT: " + className);
                flags |= TypePragmaFlags.SHAREDSTATICS;
                break;
            }
        }
        return flags;
    }

    /**
     * Combine the pragma flags for a given classname.
     *
     * @param className
     */
    private static int getClassNamePragmaFlags(String className) {
        int flags = 0;
        for (String name : SHARED_STATICS_CLASSNAMES) {
            if (className.equals(name)) {
                flags |= TypePragmaFlags.SHAREDSTATICS;
                break;
            }
        }
        return flags;
    }

    /**
     * Read a single annotation structure.
     *
     * @param data
     * @param cp
     */
    private static VmAnnotation readAnnotation(ByteBuffer data, VmCP cp, boolean visible) {
        final String typeDescr = cp.getUTF8(data.getChar());
        final int numElemValuePairs = data.getChar();
        final VmAnnotation.ElementValue[] values;
        if (numElemValuePairs == 0) {
            values = VmAnnotation.ElementValue.EMPTY_ARR;
        } else if (visible) {
            values = new VmAnnotation.ElementValue[numElemValuePairs];
            for (int i = 0; i < numElemValuePairs; i++) {
                final String elemName = cp.getUTF8(data.getChar());
                final Object value = readElementValue(data, cp);
                values[i] = new VmAnnotation.ElementValue(elemName, value);
            }
        } else {
            values = VmAnnotation.ElementValue.EMPTY_ARR;
            for (int i = 0; i < numElemValuePairs; i++) {
                data.getChar(); // Skip name ref
                skipElementValue(data, cp);
            }
        }
        return new VmAnnotation(typeDescr, values);
    }

    //todo will get obsolate with openjdk based annotation support
    /**
     * Read a single annotation structure.
     *
     * @param data
     * @param cp
     */
    private static VmAnnotation readAnnotation2(ByteBuffer data, VmCP cp,
                                                boolean visible, VmClassLoader loader, VmType vmType) {
        final String typeDescr = cp.getUTF8(data.getChar());
        final int numElemValuePairs = data.getChar();
        final VmAnnotation.ElementValue[] values;
        if (numElemValuePairs == 0) {
            values = VmAnnotation.ElementValue.EMPTY_ARR;
        } else if (visible) {
            values = new VmAnnotation.ElementValue[numElemValuePairs];
            for (int i = 0; i < numElemValuePairs; i++) {
                final String elemName = cp.getUTF8(data.getChar());

                Object defo = null; //readElementValue(data, cp);

                try {
                    VmType annType = new Signature(typeDescr, loader).getType();

                    VmMethod mts = null;
                    int dmc = annType.getNoDeclaredMethods();
                    for (int v = 0; v < dmc; v++) {
                        VmMethod m = annType.getDeclaredMethod(v);
                        if (elemName.equals(m.getName())) {
                            mts = m;
                            break;
                        }
                    }

                    Class r_class;
                    VmType vtm = mts.getReturnType();
                    if (vtm.isPrimitive()) {
                        r_class = getClassForJvmType(vtm.getJvmType());
                    } else {
                        try {
                            r_class = vtm.getLoader().asClassLoader().loadClass(vtm.getName());
                        } catch (ClassNotFoundException cnf) {
                            throw new RuntimeException(cnf);
                        }
                    }
                    Class container;
                    try {

                        container = annType.getLoader().asClassLoader().loadClass(annType.getName());
                    } catch (ClassNotFoundException cnf) {
                        throw new RuntimeException(cnf);
                    }
                    defo = AnnotationParser.parseMemberValue(r_class, data, new VmConstantPool(vmType), container);

                    if (defo instanceof ExceptionProxy)
                        throw new RuntimeException("Error parsing annotation parameter value (annotation= " +
                            annType.getName() + ", parameter=" + mts.getName() + ')');

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }


                final Object value = defo; //readElementValue(data, cp);
                values[i] = new VmAnnotation.ElementValue(elemName, value);
            }
        } else {
            values = VmAnnotation.ElementValue.EMPTY_ARR;
            for (int i = 0; i < numElemValuePairs; i++) {
                data.getChar(); // Skip name ref
                skipElementValue(data, cp);
            }
        }
        return new VmAnnotation(typeDescr, values);
    }

    /**
     * Read a single element_value structure.
     *
     * @param data
     * @param cp
     */
    private static Object readElementValue(ByteBuffer data, VmCP cp) {
        final int tag = data.get() & 0xFF;
        switch (tag) {
            case 'B':
                return (byte) cp.getInt(data.getChar());
            case 'C':
                return (char) cp.getInt(data.getChar());
            case 'D':
                return cp.getDouble(data.getChar());
            case 'F':
                return cp.getFloat(data.getChar());
            case 'I':
                return cp.getInt(data.getChar());
            case 'J':
                return cp.getLong(data.getChar());
            case 'S':
                return (short) cp.getInt(data.getChar());
            case 'Z':
                return cp.getInt(data.getChar()) != 0;
            case 's':
                return cp.getAny(data.getChar());
            case 'e': // enum
            {
                final String typeDescr = cp.getUTF8(data.getChar());
                final String constName = cp.getUTF8(data.getChar());
                return new VmAnnotation.EnumValue(typeDescr, constName);
            }
            case 'c': // class
            {
                final String classDescr = cp.getUTF8(data.getChar());
                return new VmAnnotation.ClassInfo(classDescr);
            }
            case '@': // annotation
                return readAnnotation(data, cp, true);
            case '[': // array
            {
                final int numValues = data.getChar();
                final Object[] arr = new Object[numValues];
                for (int i = 0; i < numValues; i++) {
                    arr[i] = readElementValue(data, cp);
                }
                return arr;
            }
            default:
                throw new ClassFormatError("Unknown element_value tag '"
                    + (char) tag + '\'');
        }
    }

    /**
     * Skip over a single element_value structure.
     *
     * @param data
     * @param cp
     */
    private static void skipElementValue(ByteBuffer data, VmCP cp) {
        final int tag = data.get() & 0xFF;
        switch (tag) {
            case 'B':
            case 'C':
            case 'D':
            case 'F':
            case 'I':
            case 'J':
            case 'S':
            case 'Z':
            case 's':
                data.getChar();
                break;
            case 'e': // enum
                data.getChar(); // typedescr
                data.getChar(); // constname
                break;
            case 'c': // class
                data.getChar(); // classdescr
                break;
            case '@': // annotation
                readAnnotation(data, cp, false);
                break;
            case '[': // array
            {
                final int numValues = data.getChar();
                for (int i = 0; i < numValues; i++) {
                    skipElementValue(data, cp);
                }
                break;
            }
            default:
                throw new ClassFormatError("Unknown element_value tag '"
                    + (char) tag + '\'');
        }
    }

    private static final void skip(ByteBuffer data, int delta) {
        data.position(data.position() + delta);
    }

    private static final ByteBuffer readBytes(ByteBuffer data, int length) {
        final ByteBuffer result = (ByteBuffer) data.slice().limit(length);
        data.position(data.position() + length);
        return result;
    }

    private static final String readUTF(ByteBuffer data) {
        final int utflen = data.getChar();
        final String result;
        try {
            result = VmUTF8Convert.fromUTF8(data,
                getUtfConversionBuffer(utflen), utflen);
        } catch (UTFDataFormatException ex) {
            throw (ClassFormatError) new ClassFormatError(
                "Invalid UTF sequence").initCause(ex);
        }
        return result;
    }

    private static transient ThreadLocal utfConversionBuffer;

    private static final char[] getUtfConversionBuffer(int utfLength) {
        if (utfConversionBuffer == null) {
            synchronized (ClassDecoder.class) {
                if (utfConversionBuffer == null) {
                    utfConversionBuffer = new ThreadLocal();
                }
            }
        }
        char[] buffer = (char[]) utfConversionBuffer.get();
        if ((buffer == null) || (buffer.length < utfLength)) {
            buffer = new char[Math.max(64, utfLength)];
            utfConversionBuffer.set(buffer);
        }

        return buffer;
    }

    private static final class MethodPragmaException {
        public final char flags;

        public final String className;

        public MethodPragmaException(Class<? extends PragmaException> cls,
                                     char flags) {
            this.className = cls.getName();
            this.flags = flags;
        }
    }

    private static final class PragmaInterface {
        public final char flags;

        public final String className;

        public PragmaInterface(Class<?> cls, char flags) {
            this.className = cls.getName();
            this.flags = flags;
        }
    }

    private static final class PragmaAnnotation {
        private static final String[] EMPTY_PACKAGES = new String[0];
        public final char flags;

        public final String typeDescr;

        private String[] allowedPackages;

        public PragmaAnnotation(Class<? extends Annotation> cls, char flags) {
            this.typeDescr = 'L' + cls.getName().replace('.', '/') + ';';
            this.flags = flags;
        }

        /**
         * Is this annotation allowed for the given classname.
         */
        public final void checkPragmaAllowed(String className) {
            //lazy initialization of allowPackages to avoid startup failure
            if (allowedPackages == null) {
                String class_name = typeDescr.substring(1, typeDescr.length() - 1).replace('/', '.');
                try {
                    Class<Annotation> cls = (Class<Annotation>) Class.forName(class_name);
                    final AllowedPackages ann = cls.getAnnotation(AllowedPackages.class);
                    if (ann != null) {
                        allowedPackages = ann.value();
                    } else {
                        allowedPackages = EMPTY_PACKAGES;
                    }
                } catch (ClassNotFoundException x) {
                    throw new RuntimeException(x);
                }
            }

            if (allowedPackages.length > 0) {
                final String pkg = className.substring(0, className.lastIndexOf('.'));
                for (String allowedPkg : allowedPackages) {
                    if (pkg.equals(allowedPkg)) {
                        return;
                    }
                }
                throw new SecurityException("Pragma " + typeDescr + " is not allowed in class " + className);
            }
        }
    }

    private static final class FieldData {
        public final String name;

        public final String signature;

        public final int modifiers;

        public final Object constantValue;

        public final VmAnnotation[] rVisAnn;

        public final byte[] rawAnnotations;
        /**
         * @param name
         * @param signature
         * @param modifiers
         * @param value
         * @param rVisAnn
         */
        public FieldData(String name, String signature, int modifiers,
                         Object value, VmAnnotation[] rVisAnn, byte[] rawAnnotations) {
            this.name = name;
            this.signature = signature;
            this.modifiers = modifiers;
            this.constantValue = value;
            this.rVisAnn = rVisAnn;
            this.rawAnnotations = rawAnnotations;
        }

    }
}
TOP

Related Classes of org.jnode.vm.classmgr.ClassDecoder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.