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

mockit.asm.constantPool.ConstantPoolGeneration Maven / Gradle / Ivy

package mockit.asm.constantPool;

import javax.annotation.*;

import mockit.asm.jvmConstants.*;
import mockit.asm.types.*;
import mockit.asm.util.*;
import mockit.internal.util.*;
import static mockit.asm.jvmConstants.ConstantPoolTypes.*;
import static mockit.internal.util.ClassLoad.OBJECT;

/**
 * Allows the constant pool for a classfile to be created from scratch, when that classfile itself is being generated or
 * modified from an existing class file.
 */
@SuppressWarnings({"ClassWithTooManyFields", "OverlyCoupledClass"})
public final class ConstantPoolGeneration
{
   /**
    * The constant pool of the class file being generated/modified.
    */
   @Nonnull private final ByteVector pool;

   /**
    * The constant pool's hash table data.
    */
   @Nonnull private Item[] items;

   /**
    * The threshold of the constant pool's hash table.
    */
   @Nonnegative private int threshold;

   /**
    * Index of the next item to be added in the constant pool.
    */
   @Nonnegative private int index;

   @Nonnull private final StringItem reusableUTF8Item;
   @Nonnull private final StringItem reusableStringItem;
   @Nonnull private final NameAndTypeItem reusableNameTypeItem;
   @Nonnull private final ClassMemberItem reusableClassMemberItem;
   @Nonnull private final IntItem reusableIntItem;
   @Nonnull private final LongItem reusableLongItem;
   @Nonnull private final FloatItem reusableFloatItem;
   @Nonnull private final DoubleItem reusableDoubleItem;
   @Nonnull private final MethodHandleItem reusableMethodHandleItem;
   @Nonnull private final DynamicItem reusableDynamicItem;

   /**
    * A type table used to temporarily store internal names that will not necessarily be stored in the constant pool.
    * This type table is used by the control flow and data flow analysis algorithm to compute stack map frames from scratch.
    * This array associates to each index i the TypeTableItem whose index is i.
    * All TypeTableItem objects stored in this array are also stored in the {@link #items} hash table.
    * These two arrays allow to retrieve an Item from its index or, conversely, to get the index of an Item from its
    * value. Each TypeTableItem stores an internal name in its {@link TypeTableItem#typeDesc} field.
    */
   private TypeTableItem[] typeTable;

   /**
    * Number of elements in the {@link #typeTable} array.
    */
   private short typeCount;

   @Nonnull private final NormalTypeTableItem reusableNormalItem;
   @Nonnull private final UninitializedTypeTableItem reusableUninitializedItem;
   @Nonnull private final MergedTypeTableItem reusableMergedItem;

   @SuppressWarnings("OverlyCoupledMethod")
   public ConstantPoolGeneration() {
      pool = new ByteVector();
      items = new Item[256];
      //noinspection NumericCastThatLosesPrecision
      threshold = (int) (0.75d * items.length);
      index = 1;
      reusableUTF8Item = new StringItem();
      reusableStringItem = new StringItem();
      reusableNameTypeItem = new NameAndTypeItem(0);
      reusableClassMemberItem = new ClassMemberItem(0);
      reusableIntItem = new IntItem(0);
      reusableLongItem = new LongItem(0);
      reusableFloatItem = new FloatItem(0);
      reusableDoubleItem = new DoubleItem(0);
      reusableMethodHandleItem = new MethodHandleItem(0);
      reusableDynamicItem = new DynamicItem(0);
      reusableNormalItem = new NormalTypeTableItem();
      reusableUninitializedItem = new UninitializedTypeTableItem();
      reusableMergedItem = new MergedTypeTableItem();
   }

   /**
    * Adds an UTF8 string to the constant pool of the class being built. Does nothing if the constant pool already contains a similar item.
    *
    * @param value the String value.
    * @return the index of a new or already existing UTF8 item.
    */
   @Nonnegative
   public int newUTF8(@Nonnull String value) {
      reusableUTF8Item.set(UTF8, value);

      StringItem result = get(reusableUTF8Item);

      if (result == null) {
         pool.putByte(UTF8).putUTF8(value);

         result = new StringItem(index++, reusableUTF8Item);
         put(result);
      }

      return result.index;
   }

   /**
    * Adds a class reference to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param internalName the internal name of the class.
    * @return the index of a new or already existing class reference item.
    */
   @Nonnegative
   public int newClass(@Nonnull String internalName) {
      return newClassItem(internalName).index;
   }

   /**
    * Adds a class reference to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param internalName the internal name of the class.
    * @return a new or already existing class reference item.
    */
   @Nonnull
   public StringItem newClassItem(@Nonnull String internalName) {
      return newStringItem(CLASS, internalName);
   }

   /**
    * Adds a string to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param type one of {@link ConstantPoolTypes#STR}, {@link ConstantPoolTypes#CLASS} or {@link ConstantPoolTypes#MTYPE}
    * @param value the String value.
    * @return a new or already existing string item.
    */
   @Nonnull
   private StringItem newStringItem(int type, @Nonnull String value) {
      reusableStringItem.set(type, value);

      StringItem result = get(reusableStringItem);

      if (result == null) {
         int itemIndex = newUTF8(value);
         pool.put12(type, itemIndex);

         result = new StringItem(index++, reusableStringItem);
         put(result);
      }

      return result;
   }

   /**
    * Adds a method handle to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @return a new or an already existing method type reference item.
    */
   @Nonnull
   public MethodHandleItem newMethodHandleItem(@Nonnull MethodHandle methodHandle) {
      reusableMethodHandleItem.set(methodHandle);

      MethodHandleItem result = get(reusableMethodHandleItem);

      if (result == null) {
         int tag = methodHandle.tag;
         int memberType = tag == MethodHandle.Tag.INVOKEINTERFACE ? IMETH : METH;
         ClassMemberItem memberItem = newClassMemberItem(memberType, methodHandle.owner, methodHandle.name, methodHandle.desc);
         pool.put11(HANDLE, tag).putShort(memberItem.index);

         result = new MethodHandleItem(index++, reusableMethodHandleItem);
         put(result);
      }

      return result;
   }

   @Nonnull
   private ClassMemberItem newClassMemberItem(int type, @Nonnull String owner, @Nonnull String name, @Nonnull String desc) {
      reusableClassMemberItem.set(type, owner, name, desc);

      ClassMemberItem result = get(reusableClassMemberItem);

      if (result == null) {
         int ownerItemIndex = newClass(owner);
         int nameAndTypeItemIndex = newNameType(name, desc);
         put122(type, ownerItemIndex, nameAndTypeItemIndex);

         result = new ClassMemberItem(index++, reusableClassMemberItem);
         put(result);
      }

      return result;
   }

   /**
    * Adds a field reference to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param owner the internal name of the field's owner class
    * @param name  the field's name
    * @param desc  the field's descriptor
    * @return a new or already existing field reference item
    */
   @Nonnull
   public ClassMemberItem newFieldItem(@Nonnull String owner, @Nonnull String name, @Nonnull String desc) {
      return newClassMemberItem(FIELD, owner, name, desc);
   }

   /**
    * Adds a method reference to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param owner the internal name of the method's owner class
    * @param name  the method's name
    * @param desc  the method's descriptor
    * @param itf   true if owner is an interface
    * @return a new or already existing method reference item
    */
   @Nonnull
   public ClassMemberItem newMethodItem(@Nonnull String owner, @Nonnull String name, @Nonnull String desc, boolean itf) {
      return newClassMemberItem(itf ? IMETH : METH, owner, name, desc);
   }

   /**
    * Adds an integer to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param value the int value
    * @return a new or already existing int item
    */
   @Nonnull
   public IntItem newInteger(int value) {
      reusableIntItem.setValue(value);

      IntItem result = get(reusableIntItem);

      if (result == null) {
         pool.putByte(INT).putInt(value);

         result = new IntItem(index++, reusableIntItem);
         put(result);
      }

      return result;
   }

   /**
    * Adds a float to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param value the float value
    * @return a new or already existing float item
    */
   @Nonnull
   public FloatItem newFloat(float value) {
      reusableFloatItem.set(value);

      FloatItem result = get(reusableFloatItem);

      if (result == null) {
         pool.putByte(FLOAT).putInt(reusableFloatItem.intVal);

         result = new FloatItem(index++, reusableFloatItem);
         put(result);
      }

      return result;
   }

   /**
    * Adds a long to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param value the long value
    * @return a new or already existing long item
    */
   @Nonnull
   public LongItem newLong(long value) {
      reusableLongItem.setValue(value);

      LongItem result = get(reusableLongItem);

      if (result == null) {
         pool.putByte(LONG).putLong(value);

         result = new LongItem(index, reusableLongItem);
         index += 2;
         put(result);
      }

      return result;
   }

   /**
    * Adds a double to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param value the double value
    * @return a new or already existing double item
    */
   @Nonnull
   public DoubleItem newDouble(double value) {
      reusableDoubleItem.set(value);

      DoubleItem result = get(reusableDoubleItem);

      if (result == null) {
         pool.putByte(DOUBLE).putLong(reusableDoubleItem.longVal);

         result = new DoubleItem(index, reusableDoubleItem);
         index += 2;
         put(result);
      }

      return result;
   }

   /**
    * Adds a name and type to the constant pool of the class being built. Does nothing if the constant pool already contains a similar item.
    *
    * @param name a name
    * @param desc a type descriptor
    * @return the index of a new or already existing name and type item
    */
   @Nonnegative
   private int newNameType(@Nonnull String name, @Nonnull String desc) {
      reusableNameTypeItem.set(name, desc);

      NameAndTypeItem result = get(reusableNameTypeItem);

      if (result == null) {
         int nameItemIndex = newUTF8(name);
         int descItemIndex = newUTF8(desc);
         put122(NAME_TYPE, nameItemIndex, descItemIndex);

         result = new NameAndTypeItem(index++, reusableNameTypeItem);
         put(result);
      }

      return result.index;
   }

   /**
    * Adds a number or string constant to the constant pool of the class being built.
    * Does nothing if the constant pool already contains a similar item.
    *
    * @param cst the value of the constant to be added to the constant pool, which must be an {@link Integer}, a {@link Float}, a
    * {@link Long}, a {@link Double}, a {@link String}, or a {@link JavaType}
    * @return a new or already existing constant item with the given value
    */
   @Nonnull
   public Item newConstItem(@Nonnull Object cst) {
      if (cst instanceof String) {
         return newStringItem(STR, (String) cst);
      }

      if (cst instanceof Number) {
         return newNumberItem((Number) cst);
      }

      if (cst instanceof Character) {
         return newInteger((int) (Character) cst);
      }

      if (cst instanceof Boolean) {
         int val = (Boolean) cst ? 1 : 0;
         return newInteger(val);
      }

      if (cst instanceof ReferenceType) {
         String typeDesc = ((ReferenceType) cst).getInternalName();
         return cst instanceof MethodType ? newStringItem(MTYPE, typeDesc) : newClassItem(typeDesc);
      }

      if (cst instanceof PrimitiveType) {
         String typeDesc = ((PrimitiveType) cst).getDescriptor();
         return newClassItem(typeDesc);
      }

      if (cst instanceof MethodHandle) {
         return newMethodHandleItem((MethodHandle) cst);
      }

      throw new IllegalArgumentException("value " + cst);
   }

   @Nonnull
   private Item newNumberItem(@Nonnull Number cst) {
      if (cst instanceof Float) {
         return newFloat(cst.floatValue());
      }

      if (cst instanceof Long) {
         return newLong(cst.longValue());
      }

      if (cst instanceof Double) {
         return newDouble(cst.doubleValue());
      }

      return newInteger(cst.intValue());
   }

   /**
    * Adds the given internal name to {@link #typeTable} and returns its index.
    * Does nothing if the type table already contains this internal name.
    *
    * @param type the internal name to be added to the type table
    * @return the index of this internal name in the type table
    */
   @Nonnegative
   public int addNormalType(@Nonnull String type) {
      reusableNormalItem.set(type);

      TypeTableItem result = get(reusableNormalItem);

      if (result == null) {
         result = new NormalTypeTableItem(++typeCount, reusableNormalItem);
         addToTypeTable(result);
      }

      return result.index;
   }

   /**
    * Adds the given "uninitialized" type to {@link #typeTable} and returns its index.
    * This method is used for UNINITIALIZED types, made of an internal name and a bytecode offset.
    *
    * @param type the internal name to be added to the type table
    * @param offset the bytecode offset of the NEW instruction that created this UNINITIALIZED type value
    * @return the index of this internal name in the type table
    */
   @Nonnegative
   public int addUninitializedType(@Nonnull String type, @Nonnegative int offset) {
      reusableUninitializedItem.set(type, offset);

      TypeTableItem result = get(reusableUninitializedItem);

      if (result == null) {
         result = new UninitializedTypeTableItem(++typeCount, reusableUninitializedItem);
         addToTypeTable(result);
      }

      return result.index;
   }

   private void addToTypeTable(@Nonnull TypeTableItem newItem) {
      put(newItem);

      if (typeTable == null) {
         typeTable = new TypeTableItem[16];
      }

      int newItemIndex = typeCount;
      enlargeTypeTableIfNeeded(newItemIndex);
      typeTable[newItemIndex] = newItem;
   }

   private void enlargeTypeTableIfNeeded(@Nonnegative int newItemIndex) {
      int currentTypeCount = typeTable.length;

      if (newItemIndex == currentTypeCount) {
         TypeTableItem[] newTable = new TypeTableItem[2 * currentTypeCount];
         System.arraycopy(typeTable, 0, newTable, 0, currentTypeCount);
         typeTable = newTable;
      }
   }

   /**
    * Returns the index of the common super type of the two given types. This method calls {@link #getCommonSuperClass} and caches the
    * result in the {@link #items} hash table to speedup future calls with the same parameters.
    *
    * @param type1 index of an internal name in {@link #typeTable}
    * @param type2 index of an internal name in {@link #typeTable}
    * @return the index of the common super type of the two given types
    */
   @Nonnegative
   public int getMergedType(@Nonnegative int type1, @Nonnegative int type2) {
      reusableMergedItem.set(type1, type2);

      MergedTypeTableItem result = get(reusableMergedItem);

      if (result == null) {
         String type1Desc = getInternalName(type1);
         String type2Desc = getInternalName(type2);
         String commonSuperClass = getCommonSuperClass(type1Desc, type2Desc);
         reusableMergedItem.commonSuperTypeIndex = addNormalType(commonSuperClass);

         result = new MergedTypeTableItem(reusableMergedItem);
         put(result);
      }

      return result.commonSuperTypeIndex;
   }

   /**
    * Returns the common super type of the two given types. The default implementation of this method loads the two given classes and
    * uses the java.lang.Class methods to find the common super class. It can be overridden to compute this common super type in other ways,
    * in particular without actually loading any class, or to take into account the class that is currently being generated by this
    * ClassWriter, which can of course not be loaded since it is under construction.
    *
    * @param type1 the internal name of a class
    * @param type2 the internal name of another class
    * @return the internal name of the common super class of the two given classes
    */
   @Nonnull
   private static String getCommonSuperClass(@Nonnull String type1, @Nonnull String type2) {
      // Reimplemented to avoid "duplicate class definition" errors.
      String class1 = type1;
      String class2 = type2;

      while (true) {
         if (OBJECT.equals(class1) || OBJECT.equals(class2)) {
            return OBJECT;
         }

         String superClass = ClassLoad.whichIsSuperClass(class1, class2);

         if (superClass != null) {
            return superClass;
         }

         class1 = ClassLoad.getSuperClass(class1);
         class2 = ClassLoad.getSuperClass(class2);

         if (class1.equals(class2)) {
            return class1;
         }
      }
   }

   @Nonnull
   public String getInternalName(@Nonnegative int typeTableIndex) {
      TypeTableItem typeTableItem = typeTable[typeTableIndex]; // Normal or Uninitialized
      return typeTableItem.typeDesc;
   }

   @Nonnull
   public UninitializedTypeTableItem getUninitializedItemValue(@Nonnegative int typeTableIndex) {
      return (UninitializedTypeTableItem) typeTable[typeTableIndex];
   }

   @Nullable
   public Item getItem(@Nonnegative int itemHashCode) { return items[itemHashCode % items.length]; }

   /**
    * Returns the constant pool's hash table item which is equal to the given item.
    *
    * @param key a constant pool item
    * @return the constant pool's hash table item which is equal to the given item, or null if there is no such item
    */
   @Nullable
   private  I get(@Nonnull I key) {
      Item item = getItem(key.getHashCode());
      int keyType = key.getType();

      while (item != null && (item.getType() != keyType || !key.isEqualTo(item))) {
         item = item.getNext();
      }

      //noinspection unchecked
      return (I) item;
   }

   /**
    * Puts the given item in the constant pool's hash table. The hash table must not already contains this item.
    *
    * @param item the item to be added to the constant pool's hash table
    */
   private void put(@Nonnull Item item) {
      resizeItemArrayIfNeeded();
      item.setNext(items);
   }

   private void resizeItemArrayIfNeeded() {
      if (index + typeCount > threshold) {
         int ll = items.length;
         int nl = ll * 2 + 1;
         Item[] newItems = new Item[nl];

         for (int l = ll - 1; l >= 0; l--) {
            Item j = items[l];
            put(newItems, j);
         }

         items = newItems;
         //noinspection NumericCastThatLosesPrecision
         threshold = (int) (nl * 0.75);
      }
   }

   private static void put(@Nonnull Item[] newItems, @Nullable Item item) {
      while (item != null) {
         Item next = item.getNext();
         item.setNext(newItems);
         //noinspection AssignmentToMethodParameter
         item = next;
      }
   }

   /**
    * Puts one byte and two shorts into the constant pool.
    */
   private void put122(int b, int s1, int s2) {
      pool.put12(b, s1).putShort(s2);
   }

   @Nonnegative
   public int getSize() { return pool.getLength(); }

   public void checkConstantPoolMaxSize() {
      if (index > 0xFFFF) {
         throw new RuntimeException("Class file too large!");
      }
   }

   public void put(@Nonnull ByteVector out) {
      out.putShort(index).putByteVector(pool);
   }

   public void copy(@Nonnull byte[] code, @Nonnegative int off, @Nonnegative int header, @Nonnull Item[] cpItems) {
      pool.putByteArray(code, off, header - off);
      items = cpItems;

      int ll = cpItems.length;
      //noinspection NumericCastThatLosesPrecision
      threshold = (int) (0.75d * ll);
      index = ll;
   }

   @Nonnull
   public DynamicItem createDynamicItem(int type, @Nonnull String name, @Nonnull String desc, @Nonnegative int bsmIndex) {
      reusableDynamicItem.set(type, name, desc, bsmIndex);

      DynamicItem result = get(reusableDynamicItem);

      if (result == null) {
         int nameAndTypeItemIndex = newNameType(name, desc);
         put122(type, bsmIndex, nameAndTypeItemIndex);

         result = new DynamicItem(index++, reusableDynamicItem);
         put(result);
      }

      return result;
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy