/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: ClassDef.java,v 1.1.1.1.2.1 2004/07/16 23:32:30 vlad_r Exp $ */ package com.vladium.jcd.cls; import java.io.DataOutputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import com.vladium.jcd.cls.attribute.AttributeElementFactory; import com.vladium.jcd.cls.attribute.Attribute_info; import com.vladium.jcd.cls.attribute.CodeAttribute_info; import com.vladium.jcd.cls.attribute.InnerClassesAttribute_info; import com.vladium.jcd.cls.constant.CONSTANT_Class_info; import com.vladium.jcd.cls.constant.CONSTANT_Fieldref_info; import com.vladium.jcd.cls.constant.CONSTANT_NameAndType_info; import com.vladium.jcd.cls.constant.CONSTANT_String_info; import com.vladium.jcd.cls.constant.CONSTANT_Utf8_info; import com.vladium.jcd.compiler.IClassFormatOutput; import com.vladium.jcd.lib.Types; import com.vladium.jcd.lib.UDataOutputStream; import com.vladium.util.ByteArrayOStream; // ---------------------------------------------------------------------------- /** * This class represents the abstract syntax table (AST) that {@link com.vladium.jcd.parser.ClassDefParser} * produces from bytecode. Most elements are either settable or extendible. * This class also implements {@link com.vladium.jcd.compiler.IClassFormatOutput} * and works with {@link com.vladium.jcd.compiler.ClassWriter} to produce * bytecode without an external compiler.<P> * * MT-safety: this class and all interfaces used by it are not safe for * access from multiple concurrent threads. * * @author (C) 2001, Vlad Roubtsov */ public final class ClassDef implements Cloneable, IAccessFlags, IClassFormatOutput { // public: ................................................................ public ClassDef () { m_version = new int [2]; m_constants = ElementFactory.newConstantCollection (-1); m_interfaces = ElementFactory.newInterfaceCollection (-1); m_fields = ElementFactory.newFieldCollection (-1); m_methods = ElementFactory.newMethodCollection (-1); m_attributes = ElementFactory.newAttributeCollection (-1); } // Visitor: public void accept (final IClassDefVisitor visitor, final Object ctx) { visitor.visit (this, ctx); } public long getMagic () { return m_magic; } public void setMagic (final long magic) { m_magic = magic; } public int [] getVersion () { return m_version; } public void setVersion (final int [] version) { m_version [0] = version [0]; m_version [1] = version [1]; } public final void setDeclaredSUID (final long suid) { m_declaredSUID = suid; } public int getThisClassIndex () { return m_this_class_index; } public void setThisClassIndex (final int this_class_index) { m_this_class_index = this_class_index; } public CONSTANT_Class_info getThisClass () { return (CONSTANT_Class_info) m_constants.get (m_this_class_index); } public CONSTANT_Class_info getSuperClass () { return (CONSTANT_Class_info) m_constants.get (m_super_class_index); } public String getName () { return getThisClass ().getName (this); } public int getSuperClassIndex () { return m_super_class_index; } public void setSuperClassIndex (final int super_class_index) { m_super_class_index = super_class_index; } // IAccessFlags: public final int getAccessFlags () { return m_access_flags; } public final void setAccessFlags (final int flags) { m_access_flags = flags; } public boolean isInterface () { return (m_access_flags & ACC_INTERFACE) != 0; } public boolean isSynthetic () { return m_attributes.hasSynthetic (); } public boolean isNested (final int [] nestedAccessFlags) { final InnerClassesAttribute_info innerClassesAttribute = m_attributes.getInnerClassesAttribute (); if (innerClassesAttribute == null) return false; else return innerClassesAttribute.makesClassNested (m_this_class_index, nestedAccessFlags); } // methods for getting various nested tables: public IConstantCollection getConstants () { return m_constants; } public IInterfaceCollection getInterfaces () { return m_interfaces; } public IFieldCollection getFields () { return m_fields; } public IMethodCollection getMethods () { return m_methods; } public IAttributeCollection getAttributes () { return m_attributes; } public int [] getFields (final String name) { return m_fields.get (this, name); } public int [] getMethods (final String name) { return m_methods.get (this, name); } // Cloneable: /** * Performs a deep copy. */ public Object clone () { try { final ClassDef _clone = (ClassDef) super.clone (); // do deep copy: _clone.m_version = (int []) m_version.clone (); _clone.m_constants = (IConstantCollection) m_constants.clone (); _clone.m_interfaces = (IInterfaceCollection) m_interfaces.clone (); _clone.m_fields = (IFieldCollection) m_fields.clone (); _clone.m_methods = (IMethodCollection) m_methods.clone (); _clone.m_attributes = (IAttributeCollection) m_attributes.clone (); return _clone; } catch (CloneNotSupportedException e) { throw new InternalError (e.toString ()); } } // IClassFormatOutput: public void writeInClassFormat (final UDataOutputStream out) throws IOException { if (out == null) throw new IllegalArgumentException ("null input: out"); out.writeU4 (m_magic); out.writeU2 (m_version [1]); out.writeU2 (m_version [0]); m_constants.writeInClassFormat (out); out.writeU2 (m_access_flags); out.writeU2 (m_this_class_index); out.writeU2 (m_super_class_index); m_interfaces.writeInClassFormat (out); m_fields.writeInClassFormat (out); m_methods.writeInClassFormat (out); m_attributes.writeInClassFormat (out); } public final long getDeclaredSUID () { return m_declaredSUID; } /** * This follows the spec at http://java.sun.com/j2se/1.4.1/docs/guide/serialization/spec/class.doc6.html#4100 * as well as undocumented hacks used by Sun's 1.4.2 J2SDK */ public final long computeSUID (final boolean skipCLINIT) { long result = m_declaredSUID; if (result != 0L) return result; else { try { final ByteArrayOStream bout = new ByteArrayOStream (1024); // TODO: reuse these final DataOutputStream dout = new DataOutputStream (bout); // (1) The class name written using UTF encoding: dout.writeUTF (Types.vmNameToJavaName (getName ())); // [in Java format] // (2) The class modifiers written as a 32-bit integer: // ACC_STATIC is never written for nested classes/interfaces; // however, ACC_SUPER must be ignored: { // this is tricky: for static/non-static nested classes that // were declared protected in the source the usual access flags // will have ACC_PUBLIC set; the only way to achieve J2SDK // compatibility is to recover the source access flags // from the InnerClasses attribute: final int [] nestedAccessFlags = new int [1]; final int modifiers = (isNested (nestedAccessFlags) ? nestedAccessFlags [0] : getAccessFlags ()) & (ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT); // if/when emma decides to instrument interfaces for <clinit> // coverage, compensate for javac bug in which ABSTRACT bit // was set for an interface only if the interface declared methods // [Sun's J2SDK]: dout.writeInt (modifiers); } // not doing another J2SDK compensation for arrays, because // we never load/instrument those // (3) The name of each interface sorted by name written using UTF encoding { final IInterfaceCollection interfaces = getInterfaces (); final String [] ifcs = new String [interfaces.size ()]; final int iLimit = ifcs.length; for (int i = 0; i < iLimit; ++ i) { // [in Java format] ifcs [i] = Types.vmNameToJavaName (((CONSTANT_Class_info) m_constants.get (interfaces.get (i))).getName (this)); } Arrays.sort (ifcs); for (int i = 0; i < iLimit; ++ i) { dout.writeUTF (ifcs [i]); } } // (4) For each field of the class sorted by field name (except // private static and private transient fields): // a. The name of the field in UTF encoding. // b. The modifiers of the field written as a 32-bit integer. // c. The descriptor of the field in UTF encoding { final IFieldCollection fields = getFields (); final FieldDescriptor [] fds = new FieldDescriptor [fields.size ()]; int fcount = 0; for (int f = 0, fLimit = fds.length; f < fLimit; ++ f) { final Field_info field = fields.get (f); final int modifiers = field.getAccessFlags (); if (((modifiers & ACC_PRIVATE) == 0) || ((modifiers & (ACC_STATIC | ACC_TRANSIENT)) == 0)) fds [fcount ++] = new FieldDescriptor (field.getName (this), modifiers, field.getDescriptor (this)); } if (fcount > 0) { Arrays.sort (fds, 0, fcount); for (int i = 0; i < fcount; ++ i) { final FieldDescriptor fd = fds [i]; dout.writeUTF (fd.m_name); dout.writeInt (fd.m_modifiers); dout.writeUTF (fd.m_descriptor); } } } // (5) If a class initializer exists, write out the following: // a. The name of the method, <clinit>, in UTF encoding. // b. The modifier of the method, ACC_STATIC, written as a 32-bit integer. // c. The descriptor of the method, ()V, in UTF encoding. // (6) For each non-private constructor sorted by method name and signature: // a. The name of the method, <init>, in UTF encoding. // b. The modifiers of the method written as a 32-bit integer. // c. The descriptor of the method in UTF encoding. // (7) For each non-private method sorted by method name and signature: // a. The name of the method in UTF encoding. // b. The modifiers of the method written as a 32-bit integer. // c. The descriptor of the method in UTF encoding. // note: although this is not documented, J2SDK code uses '.''s as // descriptor separators (this is done for methods only, not for fields) { final IMethodCollection methods = getMethods (); boolean hasCLINIT = false; final ConstructorDescriptor [] cds = new ConstructorDescriptor [methods.size ()]; final MethodDescriptor [] mds = new MethodDescriptor [cds.length]; int ccount = 0, mcount = 0; for (int i = 0, iLimit = cds.length; i < iLimit; ++ i) { final Method_info method = methods.get (i); final String name = method.getName (this); if (! hasCLINIT && IClassDefConstants.CLINIT_NAME.equals (name)) { hasCLINIT = true; continue; } else { final int modifiers = method.getAccessFlags (); if ((modifiers & ACC_PRIVATE) == 0) { if (IClassDefConstants.INIT_NAME.equals (name)) cds [ccount ++] = new ConstructorDescriptor (modifiers, method.getDescriptor (this)); else mds [mcount ++] = new MethodDescriptor (name, modifiers, method.getDescriptor (this)); } } } if (hasCLINIT && ! skipCLINIT) { dout.writeUTF (IClassDefConstants.CLINIT_NAME); dout.writeInt (ACC_STATIC); dout.writeUTF (IClassDefConstants.CLINIT_DESCRIPTOR); } if (ccount > 0) { Arrays.sort (cds, 0, ccount); for (int i = 0; i < ccount; ++ i) { final ConstructorDescriptor cd = cds [i]; dout.writeUTF (IClassDefConstants.INIT_NAME); dout.writeInt (cd.m_modifiers); dout.writeUTF (cd.m_descriptor.replace ('/', '.')); } } if (mcount > 0) { Arrays.sort (mds, 0, mcount); for (int i = 0; i < mcount; ++ i) { final MethodDescriptor md = mds [i]; dout.writeUTF (md.m_name); dout.writeInt (md.m_modifiers); dout.writeUTF (md.m_descriptor.replace ('/', '.')); } } } dout.flush(); if (DEBUG_SUID) { byte [] dump = bout.copyByteArray (); for (int x = 0; x < dump.length; ++ x) { System.out.println ("DUMP[" + x + "] = " + dump [x] + "\t" + (char) dump[x]); } } final MessageDigest md = MessageDigest.getInstance ("SHA"); md.update (bout.getByteArray (), 0, bout.size ()); final byte [] hash = md.digest (); if (DEBUG_SUID) { for (int x = 0; x < hash.length; ++ x) { System.out.println ("HASH[" + x + "] = " + hash [x]); } } // final int hash0 = hash [0]; // final int hash1 = hash [1]; // result = ((hash0 >>> 24) & 0xFF) | ((hash0 >>> 16) & 0xFF) << 8 | ((hash0 >>> 8) & 0xFF) << 16 | ((hash0 >>> 0) & 0xFF) << 24 | // ((hash1 >>> 24) & 0xFF) << 32 | ((hash1 >>> 16) & 0xFF) << 40 | ((hash1 >>> 8) & 0xFF) << 48 | ((hash1 >>> 0) & 0xFF) << 56; for (int i = Math.min (hash.length, 8) - 1; i >= 0; -- i) { result = (result << 8) | (hash [i] & 0xFF); } return result; } catch (IOException ioe) { throw new Error (ioe.getMessage ()); } catch (NoSuchAlgorithmException nsae) { throw new SecurityException (nsae.getMessage()); } } } public int addCONSTANT_Utf8 (final String value, final boolean keepUnique) { if (keepUnique) { final int existing = m_constants.findCONSTANT_Utf8 (value); if (existing > 0) { return existing; } // [else fall through] } return m_constants.add (new CONSTANT_Utf8_info (value)); } public int addStringConstant (final String value) { final int value_index = addCONSTANT_Utf8 (value, true); // TODO: const uniqueness return m_constants.add (new CONSTANT_String_info (value_index)); } public int addNameType (final String name, final String typeDescriptor) { final int name_index = addCONSTANT_Utf8 (name, true); final int descriptor_index = addCONSTANT_Utf8 (typeDescriptor, true); return m_constants.add (new CONSTANT_NameAndType_info (name_index, descriptor_index)); } public int addClassref (final String classJVMName) { final int name_index = addCONSTANT_Utf8 (classJVMName, true); // TODO: this should do uniqueness checking: return m_constants.add (new CONSTANT_Class_info (name_index)); } /** * Adds a new declared field to this class [with no attributes] */ public int addField (final String name, final String descriptor, final int access_flags) { // TODO: support Fields with initializer attributes? // TODO: no "already exists" check done here final int name_index = addCONSTANT_Utf8 (name, true); final int descriptor_index = addCONSTANT_Utf8 (descriptor, true); final Field_info field = new Field_info (access_flags, name_index, descriptor_index, ElementFactory.newAttributeCollection (0)); return m_fields.add (field); } /** * Adds a new declared field to this class [with given attributes] */ public int addField (final String name, final String descriptor, final int access_flags, final IAttributeCollection attributes) { // TODO: support Fields with initializer attributes? // TODO: no "already exists" check done here final int name_index = addCONSTANT_Utf8 (name, true); final int descriptor_index = addCONSTANT_Utf8 (descriptor, true); final Field_info field = new Field_info (access_flags, name_index, descriptor_index, attributes); return m_fields.add (field); } // TODO: rework this API public Method_info newEmptyMethod (final String name, final String descriptor, final int access_flags) { // TODO: flag for making synthetic etc final int attribute_name_index = addCONSTANT_Utf8 (Attribute_info.ATTRIBUTE_CODE, true); final int name_index = addCONSTANT_Utf8 (name, true); final int descriptor_index = addCONSTANT_Utf8 (descriptor, true); final IAttributeCollection attributes = ElementFactory.newAttributeCollection (0); final CodeAttribute_info code = new CodeAttribute_info (attribute_name_index, 0, 0, CodeAttribute_info.EMPTY_BYTE_ARRAY, AttributeElementFactory.newExceptionHandlerTable (0), ElementFactory.newAttributeCollection (0)); attributes.add (code); final Method_info method = new Method_info (access_flags, name_index, descriptor_index, attributes); return method; } public int addMethod (final Method_info method) { return m_methods.add (method); } /** * Adds a reference to a field declared by this class. * * @return constant pool index of the reference */ public int addFieldref (final Field_info field) { // TODO: keepUnique flag final CONSTANT_NameAndType_info nametype = new CONSTANT_NameAndType_info (field.m_name_index, field.m_descriptor_index); final int nametype_index = m_constants.add (nametype); // TODO: unique logic return m_constants.add (new CONSTANT_Fieldref_info (m_this_class_index, nametype_index)); } /** * Adds a reference to a field declared by this class. * * @return constant pool index of the reference */ public int addFieldref (final int offset) { // TODO: keepUnique flag final Field_info field = m_fields.get (offset); final CONSTANT_NameAndType_info nametype = new CONSTANT_NameAndType_info (field.m_name_index, field.m_descriptor_index); final int nametype_index = m_constants.add (nametype); // TODO: unique logic return m_constants.add (new CONSTANT_Fieldref_info (m_this_class_index, nametype_index)); } // protected: ............................................................. // package: ............................................................... // private: ............................................................... private static final class FieldDescriptor implements Comparable { // Comparable: public final int compareTo (final Object obj) { return m_name.compareTo (((FieldDescriptor) obj).m_name); } FieldDescriptor (final String name, final int modifiers, final String descriptor) { m_name = name; m_modifiers = modifiers; m_descriptor = descriptor; } final String m_name; final int m_modifiers; final String m_descriptor; } // end of nested class private static final class ConstructorDescriptor implements Comparable { // Comparable: public final int compareTo (final Object obj) { return m_descriptor.compareTo (((ConstructorDescriptor) obj).m_descriptor); } ConstructorDescriptor (final int modifiers, final String descriptor) { m_modifiers = modifiers; m_descriptor = descriptor; } final int m_modifiers; final String m_descriptor; } // end of nested class private static final class MethodDescriptor implements Comparable { // Comparable: public final int compareTo (final Object obj) { final MethodDescriptor rhs = (MethodDescriptor) obj; int result = m_name.compareTo (rhs.m_name); if (result == 0) result = m_descriptor.compareTo (rhs.m_descriptor); return result; } MethodDescriptor (final String name, final int modifiers, final String descriptor) { m_name = name; m_modifiers = modifiers; m_descriptor = descriptor; } final String m_name; final int m_modifiers; final String m_descriptor; } // end of nested class // TODO: final fields private long m_magic; private int [] /* major, minor */ m_version; private int m_access_flags; private int m_this_class_index, m_super_class_index; private IConstantCollection m_constants; private IInterfaceCollection m_interfaces; private IFieldCollection m_fields; private IMethodCollection m_methods; private IAttributeCollection m_attributes; private long m_declaredSUID; private static final boolean DEBUG_SUID = false; } // end of class // ----------------------------------------------------------------------------