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

gnu.jel.ClassFile Maven / Gradle / Ivy

There is a newer version: 0.8.1
Show newest version
/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- 
 * $Id: ClassFile.java 490 2006-10-01 16:08:04Z metlov $
 *
 * This file is part of the Java Expressions Library (JEL).
 *   For more information about JEL visit :
 *    http://kinetic.ac.donetsk.ua/JEL/
 *
 * (c) 1998 -- 2007 by Konstantin Metlov([email protected]);
 *
 * JEL is Distributed under the terms of GNU General Public License.
 *    This code comes with ABSOLUTELY NO WARRANTY.
 *  For license details see COPYING file in this directory.
 */

package gnu.jel;

import gnu.jel.debug.Debug;
import java.lang.reflect.Member;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Stack;
import java.util.Vector;

/**
 * This class represents a classfile image in JEL.
 */
public class ClassFile implements Cloneable {

  // constant pool handling
  private int poolEntries=1; // Number of entries in the constant pool
  // the class name is in the first element of the CP
  // it is not written by default to enable the dynamic class renaming.
  private ByteArrayOutputStream constPoolData;
  private DataOutputStream constPool;

  // Constant pool hashing :
  // In next hashtables keys are Objects, values their CP indices
  // UTFs require special handling since their keys can be the same as
  // ones of strings
  private HashMap Items=new HashMap();
  private HashMap  UTFs=new HashMap();
  
  // holds code of all methods
  private int nMethods=0;
  private int nMethodsPatch;

  // holds code of all methods
  private byte[] text;
  int tsize=0;
  
  // write a byte
  void write(int b) {
    try {
      text[tsize++]=(byte)b;
    } catch (ArrayIndexOutOfBoundsException exc) {
      byte[] new_text=new byte[text.length<<1];
      System.arraycopy(text, 0, new_text, 0, tsize-1);
      text=new_text;
      text[tsize-1]=(byte)b;
    };
  };

  // write short
  void writeShort(int v) {
    write((v >>> 8) & 0xFF);
    write( v        & 0xFF);
  };

  // write int
  void writeInt(int v) {
    write((v >>> 24) & 0xFF);
    write((v >>> 16) & 0xFF);
    write((v >>>  8) & 0xFF);
    write( v         & 0xFF);
  };
  

  private boolean isInterface;

  private IntegerStack[] cstk=new IntegerStack[6]; 
  // six stacks
  //   0) "false" jumps
  //   1) "true"  jumps
  //   2) unconditional jumps
  //   3) "false" blocks
  //   4) "true" blocks
  //   6) unconditional blocks

  /**
   * Starts creation of a new class file.
   * 

Sizes of fAccess, fNames and fTypes * arrays must be the same. * @param modifiers sum of one or more of PUBLIC, FINAL, * INTERFACE, ABSTRACT * constants of java.lang.reflect.Modifier * @param name is the name of new class (must be in Java historical form, * i.e. with dots replaced by slashes '/') * @param superClass is the superclass of this class * @param interfaces array of interfaces this class implements * @param fields fields this class will have */ public ClassFile(int modifiers,String name,Class superClass, Class[] interfaces,LocalField[] fields) { constPoolData=new ByteArrayOutputStream(); constPool=new DataOutputStream(constPoolData); text=new byte[256]; if (Debug.enabled) Debug.check(cstk.length==6); for (int i=0;i<6;i++) cstk[i]=new IntegerStack(); try { if (Debug.enabled) // check if the name is in historical form Debug.check(name.indexOf('.')==-1); getUTFIndex(name); // must be the first entry if (Debug.enabled) Debug.check((modifiers & ~(java.lang.reflect.Modifier.PUBLIC+ java.lang.reflect.Modifier.FINAL+ java.lang.reflect.Modifier.INTERFACE+ java.lang.reflect.Modifier.ABSTRACT))==0); isInterface=((modifiers & 0x0200)>0); // write modifiers writeShort(modifiers | 0x0020); // set ACC_SUPER flag // CONSTANT_CLASS for a new class, has to be written by hand // since no corresponding java.lang.Class object exists yet. if (Debug.enabled) Debug.check(poolEntries==2); poolEntries++; constPool.write(7); constPool.writeShort(1); //name of this class is always the first entry. // write class cp index writeShort(2); // write superclass cp index writeShort(getIndex(superClass,9)); // write out interfaces int nInterfaces; if (interfaces==null) nInterfaces=0; else nInterfaces=interfaces.length; writeShort(nInterfaces); for(int i=0;i This is used in JEL to avoid step-by step creation of service * methods of gnu.jel.CompiledExpression (, getType, ...). They are * created only once, and then the resulting ClassFile is cloned * at the point, when it is ready to accept the code for evaluation method. * @return a clone of this object */ public ClassFile clone() { ClassFile res=null; try { res=(ClassFile)super.clone(); // clone all primitive members // clone containers res.Items=new HashMap(res.Items); res.UTFs=new HashMap(res.UTFs); res.paramsVars=(int [])res.paramsVars.clone(); res.branchStack=res.branchStack.copy(); if (Debug.enabled) Debug.check(cstk.length==6); res.cstk=new IntegerStack[6]; for(int i=0;i<6;i++) res.cstk[i]=cstk[i].copy(); // clone streams res.constPoolData=new ByteArrayOutputStream(); constPool.flush(); constPoolData.writeTo(res.constPoolData); res.constPool=new DataOutputStream(res.constPoolData); res.text=(byte[]) res.text.clone(); } catch (IOException exc) { if (Debug.enabled) Debug.reportThrowable(exc); } catch (CloneNotSupportedException exc) { if (Debug.enabled) Debug.reportThrowable(exc); }; return res; }; private int startCodeAttr=0; private int startCode=0; /** * Starts a new method of this class. * @param m method descriptor. * @param vars types of local variables by number. */ public void newMethod(LocalMethod m, Class[] vars) { if (Debug.enabled) { Debug.check(cW==0); for(int i=0;i<6;i++) Debug.check(cstk[i].size()==0); Debug.check(currJump==0); }; // first finish the last method finishMethod(); nMethods++; // now prepare creation of a new one. int mdfrs=m.getModifiers(); if (isInterface) mdfrs=mdfrs | 0x0400; // mark abstract for interfaces boolean isAbstract=((mdfrs & 0x0400) > 0); writeShort(mdfrs); // modifiers writeShort(getUTFIndex(m.getName())); // name index writeShort(getUTFIndex(Library.getSignature(m))); // signature index int temp=0; Class[] exceptions=m.getExceptionTypes(); if (exceptions!=null) temp++; // exceptions are in separate attribute if (!isAbstract) temp++; // non-abstract methods have code writeShort(temp); // first we write exceptions attribute, if needed if (exceptions!=null) { temp=exceptions.length; writeShort(getUTFIndex("Exceptions")); writeInt((temp+1)*2); // attribute length writeShort(temp); for(int i=0;i>> 8) & 0xFF); image.write((poolEntries >>> 0) & 0xFF); constPoolData.writeTo(image); image.write(text,0,tsize); image.write(0); // no class file attributes image.write(0); // no class file attributes } catch (IOException exc) { // can't be }; return image.toByteArray(); }; //========================================================= //========== INSTRUCTIONS INTERFACE TO THE STATE=========== //========================================================= private LocalMethod currMethod=null; int[] paramsVars=null; // correspondence between parameter number and lvars. // current, and maximum number of words in Java stack private int cW=0, mW=0; /** Notes removal of typeID s and subsequent addition of typeID a * to Java stack. *

If either is negative the corresponding operation * (addition/removal) is skipped. This method is needed to allow ClassFile * to compute the maximum stack occupation for the generated code. It is * responsibility of the user (of this class) to call noteStk() each * time the stack is changed from within the code. * @param s typeid to be put on stack (-1 if none). * @param a typeid to be taken off Java stack (-1 if none). */ public void noteStk(int s,int a) { if (Debug.enabled) Debug.check(cW<=mW); if ((s>=0) && (s!=9)) { cW--; if ((s & (~2)) == 5) cW--; // Note additional word for J and D if (Debug.enabled) Debug.check(cW>=0); }; if ((a>=0) && (a!=9)) { cW++; if ((a & (~2)) == 5) cW++; // Note additional word for J and D if (cW>mW) mW=cW; }; }; /** * classes frequently used in generated code */ private static final Class[] specialClasses; static { if (Debug.enabled) Debug.check(OP.specialTypes.length==29, "You changed special types in TypesStack please update "+ "specialClasses array in ClassFile."); specialClasses=(Class[])TableKeeper.getTable("specialClasses"); }; private static final Member[] specialMethods; static { char[][] specialMds=(char[][]) TableKeeper.getTable("specialMds"); String[] specialMdsN=(String[]) TableKeeper.getTable("specialMdsN"); specialMethods=new Member[specialMds.length]; { Class definingClass=null; String name=null; Class[] params=null; int i=0; try { for (i=0;i=beforeStk); }; // remove the result of the previous branch, the other branch must // put the same thing back into the types stack cW=beforeStk; code(0xEDECF2A7L); //| A7 -- goto //| F2 -- make down pointing unconditional label //| EC -- block unconditional labels //| ED -- land unblocked "false" labels break; case 2: // opc=230 (0xE6) -- finish "false" branch if (Debug.enabled) { code(0xFA); // FA -- ensure_value(); Debug.check((branchStack.pop()==cW), "Stack mismatch when compiling conditional"); code(0xEFE9); // E9 -- unblock unconditional jumps // EF -- land unblocked unconditional labels } else { code(0xEFE9FA); // FA -- ensure_value(); // E9 -- unblock unconditional jumps // EF -- land unblocked unconditional labels }; break; case 3: // opc=231 (0xE7) -- unblock "false" labels case 4: // opc=232 (0xE8) -- unblock "true" labels case 5: // opc=233 (0xE9) -- unblock unconditional labels cstk[opc-(mc+3-3)].pop(); break; case 6: // opc=234 (0xEA) -- block "false" labels case 7: // opc=235 (0xEB) -- block "true" labels case 8: // opc=236 (0xEC) -- block unconditional labels cstk[opc-(mc+6-3)].push(cstk[opc-(mc+6)].size()); break; case 9: // opc=237 (0xED) -- land unblocked "false" labels case 10: // opc=238 (0xEE) -- land unblocked "true" labels case 11: // opc=239 (0xEF) -- land unblocked unconditional labels { int blocked_at=0; final IntegerStack blk=cstk[opc-(mc+9-3)]; final IntegerStack jmp=cstk[opc-(mc+9 )]; if (blk.size()>0) blocked_at=blk.peek(); while (jmp.size()>blocked_at) { int currpos=tsize; int addrpos=jmp.pop(); // in the next command -1 because displacement is counted from the // jump opcode tsize=addrpos; writeShort(currpos-addrpos+1); tsize=currpos; }; }; break; case 12: // opc=240 (0xF0) -- make down pointing "false" label (j0) case 13: // opc=241 (0xF1) -- make down pointing "true" label (j1) case 14: // opc=242 (0xF2) -- make down pointing unconditional label cstk[opc-(mc+12 )].push(tsize); writeShort(0); // placeholder for the backpatched address break; case 15: // opc=243 (0xF3) -- set current open jump (code in next byte) currJump=(int)((op=op>>>8) & 0xFF); break; case 16: // opc=244 (0xF4) -- logical param AND case 17: // opc=245 (0xF5) -- logical param OR { // for AND s=0 for OR s=1 int s=(opc-(mc+16)); code(0xF9); // ensure jump if (iNJ ^ (s==0)) { if (Debug.enabled) Debug.check((currJump>=153) && (currJump<=166), "Attempt to invert non jump bytecode ("+ currJump+")"); currJump=(((currJump-1) ^ 0x0001)+1); }; iNJ=false; code(0x00F300+currJump); // 00F3 -- set curr jump to 0 code((0xEAEDF0) + (s<<16)+((s^1)<<8)+s); // code is // F0 - make label j0 modified by s // ED - land labels j1 modified by s^1 // EA - block labels j0 modified by s }; break; case 18: // opc=246 (0xF6) -- logical end AND case 19: // opc=247 (0xF7) -- logical end OR cstk[opc-(mc+18-3)].pop(); // just throw the corresponding block break; case 20: // opc 248 (0xF8) -- unblock all labels code(0xE7E8E9); break; case 21: // opc=249 (0xF9) -- ensure jump is in progress if (currJump==0) { // if no jump in progress yet noteStk(0,-1); // "boolean" is removed from stack currJump=157; //| }; break; case 22: // opc=250 (0xFA) -- ensure value is in stack boolean noPendingJumps=false; if (currJump==0) { // check if there are pending jumps int blocked0=0; if (cstk[3].size()>0) blocked0=cstk[3].peek(); int blocked1=0; if (cstk[4].size()>0) blocked1=cstk[4].peek(); noPendingJumps=(cstk[0].size()==blocked0) && (cstk[1].size()==blocked1); }; if (!noPendingJumps) { // if there are pending jumps or jump in progress code(0xE4); // branch_true(); codeLDC(Boolean.TRUE,0); code(0xE5); // branch_false(); codeLDC(Boolean.FALSE,0); code(0xE6); // branch_end(); }; break; case 23: // opc=251 (0xFB) -- block labels code(0xEAEBEC); break; case 24: // opc==252 (0xFC) -- unblock labels with inversion code(0xF9); // ensure jump IntegerStack.swap(cstk[1],cstk[4].pop(),cstk[0],cstk[3].pop()); cstk[5].pop(); iNJ=!iNJ; break; case 25: // opc==253 (0xFD) -- write special class CP ref op=op >>> 8; writeShort(getIndex(specialClasses[(int)(op & 0xFFL)],9)); break; case 26: // opc==254 (0xFE) -- call special method op=op >>> 8; codeM(specialMethods[(int)(op & 0xFF)]); break; default: if (Debug.enabled) Debug.check(opc !=0xFF); write(opc); }; op=op >>> 8; }; }; // Shortcut load opcodes for int type // index is value+1; allowed values from -1 to 5. // other values should be loaded from CP. // private final static int[] load_ints= // { 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // -1 0 1 2 3 4 5 // private final static int[] load_long_ints= // {0x8502, 0x09, 0x0A,0x8505,0x8506,0x8507,0x8508}; // -1 0 1 2 3 4 5 /** * generates code for code loading constant of primitive type or string. * @param o reflection object or String containing the constant * @param primitiveID type ID to save lookups. */ public final void codeLDC(Object o, int primitiveID) { if (Debug.enabled) Debug.check(((primitiveID>=0) && (primitiveID<8)) || ((primitiveID==8) && (o==null)) || ((primitiveID==10) && (o instanceof StringBuffer)) || ((primitiveID==11) && (o instanceof String))); int short_opcodes=0; boolean tsb_store=false; int iv=-1; switch (primitiveID) { case 0: iv=(((Boolean)o).booleanValue()?1:0); case 2: if (iv<0) iv=(int)((Character)o).charValue(); case 1: case 3: case 4: if (iv<0) iv=((Number)o).intValue(); if ((iv>=-1) && (iv<=5)) short_opcodes=iv+3; //| iconst_ else if ((iv>=-128) && (iv<=127)) short_opcodes=0x00000010 | ((iv & 0xFF)<<8); //| bipush, break; case 5: long lv=((Long)o).longValue(); if (((lv | 1)^1) == 0) { // 0 or 1 short_opcodes=0x09+(int)lv; } else if ((lv>=-1) && (lv<=5)) { short_opcodes=0x8503+(int)lv; }; break; case 6: float fv=((Float)o).floatValue(); if (fv==0.0f) short_opcodes=0x0B; //| fconst_0 else if (fv==1.0f) short_opcodes=0x0C; //| fconst_1 else if (fv==2.0f) short_opcodes=0x0D; //| fconst_2 break; case 7: double dv=((Double)o).doubleValue(); if (dv==0.0) short_opcodes=0x0E; //| dconst_0 else if (dv==1.0) short_opcodes=0x0F; //| dconst_1 break; case 8: if (o==null) short_opcodes=0x01; //| aconst_null break; case 10: tsb_store=true; case 11: short_opcodes=0; primitiveID=8; break; default: if (Debug.enabled) Debug.check(false,"Loading of object constants is not supported by "+ "the Java class files."); }; if (short_opcodes==0) { // longs and doubles occupy two elements of stack all others just one boolean dword_const=((primitiveID==5) || (primitiveID==7)); int cpindex; if (tsb_store) { code(0x0001FE591DFDBBL); // STR => TSB // | new // // | dup // | invokespecial StringBuffer() noteStk(-1,10); // pushes ref to TSB cpindex=getIndex(o.toString(),primitiveID); } else { // makes use of the fact that primitiveID(Object)==typeID(string) cpindex=getIndex(o,primitiveID); }; if (Debug.enabled) Debug.check((cpindex>=0) && (cpindex<=65535)); if ((!dword_const) && (cpindex<=255)) { write(0x12); //| ldc write(cpindex); } else { int opc=0x13; //| ldc_w if (dword_const) opc++; //| ldc2_w write(opc); writeShort(cpindex); }; } else { codeB(short_opcodes); } noteStk(-1,primitiveID); // add what was loaded if (tsb_store) { code(0x08FE); noteStk(11,-1); // remove extra string used up by constructor }; }; //========================================================= //================= CONSTANTS POOL HANDLING =============== //========================================================= /** * Used to get the index of the given UTF8 string in the Constant Pool. *

If the specified string was not found in the pool -- it is added. * @param str the string to look for ( to add ) * @return index of the string in the Constant Pool. */ int getUTFIndex(String str) { // Check if it is in the pool already Integer index=UTFs.get(str); if (index==null) { // add UTF to the pool index=new Integer(poolEntries++); try { constPool.write(1); // CONSTANT_Utf8 = 1; constPool.writeUTF(str); } catch (java.io.IOException e) { if (Debug.enabled) Debug.reportThrowable(e); }; UTFs.put(str,index); }; return index.intValue(); }; // encodes types of relevant objects as integers // for classes corresponding to primitive types codes are the same as // primitiveID's private int typeID(Object item) { int id=OP.typeIDObject(item); if (id<8) return id; if (item instanceof String) return 8; if (item instanceof Class) return 9; if (item instanceof Member) return 10; return -1; }; /** * Used to determine an old CP index or to create a new one for an item. * @param item an item to create or get an index for * @return index for an item (negative if it has to be written) */ private final int getIndex(Object item) { return getIndex(item,typeID(item)); }; /** * Used to determine an old CP index or to create a new one for an item. * @param item an item to create or get an index for * @param typeid identifies type of argument to avoid linear searches * @return index for an item (negative if it has to be written) */ public int getIndex(Object item,int typeid) { Integer index=Items.get(item); if (index==null) { int newIndex=-1; try { int ival=-1; switch (typeid) { case 0: ival=((Boolean)item).booleanValue()?1:0; case 2: if (ival<0) ival=(int)((Character)item).charValue(); case 1: case 3: case 4: if (ival<0) ival=((Number)item).intValue(); newIndex=poolEntries++; constPool.write(3); // CONSTANT_Integer = 3; constPool.writeInt(ival); break; case 5: newIndex=poolEntries; constPool.write(5); // CONSTANT_Long = 5; constPool.writeLong(((Long)item).longValue()); poolEntries+=2; // Long occupies two entries in CP, weird !!! break; case 6: newIndex=poolEntries++; constPool.write(4); // CONSTANT_Float = 4; constPool.writeFloat(((Float)item).floatValue()); break; case 7: newIndex=poolEntries; constPool.write(6); // CONSTANT_Double = 6; constPool.writeDouble(((Double)item).doubleValue()); poolEntries+=2; // Double occupies two entries in CP, weird !!! break; case 8: { int UTFIndex=getUTFIndex((String)item); // write string , if needed newIndex=poolEntries++; constPool.write(8); // CONSTANT_String = 8; constPool.writeShort(UTFIndex); } break; case 9: { String histNameStr= Library.toHistoricalForm(((Class)item).getName()); int UTFIndex=getUTFIndex(histNameStr); // write FQCN , if needed newIndex=poolEntries++; constPool.write(7); // CONSTANT_Class = 7; constPool.writeShort(UTFIndex); } break; case 10: // Method case 11: // Constructor case 12: // Field Member member = (Member) item; Class dClass=member.getDeclaringClass(); int entryType; if (Library.isField(member)) entryType=9; // CONSTANT_Fieldref = 9; else if ((dClass!=null) && (dClass.isInterface())) entryType=11; // CONSTANT_InterfaceMethodref = 11; else entryType=10; // CONSTANT_Methodref = 10; newIndex=writeMemberRef(member,entryType); break; default: if (Debug.enabled) Debug.println("Can't place an item of type \""+ item.getClass().getName()+ "\" to the constant pool."); }; } catch (java.io.IOException e) { if (Debug.enabled) Debug.reportThrowable(e); }; index=new Integer(newIndex); Items.put(item,index); }; return index.intValue(); }; // writes out full reference to method, interface or field // this includes UTFs, Name&Type and XXX_ref entries private int writeMemberRef(Member member, int entry) throws java.io.IOException { if (Debug.enabled) Debug.check((entry==10)||(entry==9)||(entry==11)); // CONSTANT_Fieldref = 9; CONSTANT_Methodref = 10; // CONSTANT_InterfaceMethodref = 11; int name_ind=getUTFIndex((member instanceof Constructor)?"": member.getName()); int sign_ind=getUTFIndex(Library.getSignature(member)); Class dClass=member.getDeclaringClass(); int cls_ind; if (dClass==null) cls_ind=2; else cls_ind=getIndex(dClass,9); // this class ---^^^^^^^^^ 9 means Class--^ // Create Name and Type record int nat_ind=poolEntries++; constPool.write(12); // CONSTANT_NameAndType = 12; constPool.writeShort(name_ind); constPool.writeShort(sign_ind); // Create XXX_ref entry (where XXX is InterfaceMethod, Method or field) int index=poolEntries++; constPool.write(entry); constPool.writeShort(cls_ind); constPool.writeShort(nat_ind); return index; }; //================================================================ //================== END OF CONSTANT POOL HANDLING =============== //================================================================ };





© 2015 - 2024 Weber Informatics LLC | Privacy Policy