/* * $Id$ * * Copyright (C) 2003-2015 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; } } }