View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.asm.constantPool;
7   
8   import static mockit.asm.jvmConstants.ConstantPoolTypes.CLASS;
9   import static mockit.asm.jvmConstants.ConstantPoolTypes.DOUBLE;
10  import static mockit.asm.jvmConstants.ConstantPoolTypes.FIELD_REF;
11  import static mockit.asm.jvmConstants.ConstantPoolTypes.FLOAT;
12  import static mockit.asm.jvmConstants.ConstantPoolTypes.IMETHOD_REF;
13  import static mockit.asm.jvmConstants.ConstantPoolTypes.INTEGER;
14  import static mockit.asm.jvmConstants.ConstantPoolTypes.LONG;
15  import static mockit.asm.jvmConstants.ConstantPoolTypes.METHOD_HANDLE;
16  import static mockit.asm.jvmConstants.ConstantPoolTypes.METHOD_REF;
17  import static mockit.asm.jvmConstants.ConstantPoolTypes.METHOD_TYPE;
18  import static mockit.asm.jvmConstants.ConstantPoolTypes.NAME_TYPE;
19  import static mockit.asm.jvmConstants.ConstantPoolTypes.STRING;
20  import static mockit.asm.jvmConstants.ConstantPoolTypes.UTF8;
21  import static mockit.internal.util.ClassLoad.OBJECT;
22  
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  import edu.umd.cs.findbugs.annotations.Nullable;
25  
26  import mockit.asm.jvmConstants.ConstantPoolTypes;
27  import mockit.asm.types.JavaType;
28  import mockit.asm.types.MethodType;
29  import mockit.asm.types.PrimitiveType;
30  import mockit.asm.types.ReferenceType;
31  import mockit.asm.util.ByteVector;
32  import mockit.asm.util.MethodHandle;
33  import mockit.internal.util.ClassLoad;
34  
35  import org.checkerframework.checker.index.qual.NonNegative;
36  
37  /**
38   * Allows the constant pool for a classfile to be created from scratch, when that classfile itself is being generated or
39   * modified from an existing class file.
40   */
41  @SuppressWarnings({ "ClassWithTooManyFields", "OverlyCoupledClass" })
42  public final class ConstantPoolGeneration {
43      /**
44       * The constant pool of the class file being generated/modified.
45       */
46      @NonNull
47      private final ByteVector pool;
48  
49      /**
50       * The constant pool's hash table data.
51       */
52      @NonNull
53      private Item[] items;
54  
55      /**
56       * The threshold of the constant pool's hash table.
57       */
58      @NonNegative
59      private int threshold;
60  
61      /**
62       * Index of the next item to be added in the constant pool.
63       */
64      @NonNegative
65      private int index;
66  
67      @NonNull
68      private final StringItem reusableUTF8Item;
69      @NonNull
70      private final StringItem reusableStringItem;
71      @NonNull
72      private final NameAndTypeItem reusableNameTypeItem;
73      @NonNull
74      private final ClassMemberItem reusableClassMemberItem;
75      @NonNull
76      private final IntItem reusableIntItem;
77      @NonNull
78      private final LongItem reusableLongItem;
79      @NonNull
80      private final FloatItem reusableFloatItem;
81      @NonNull
82      private final DoubleItem reusableDoubleItem;
83      @NonNull
84      private final MethodHandleItem reusableMethodHandleItem;
85      @NonNull
86      private final DynamicItem reusableDynamicItem;
87  
88      /**
89       * A type table used to temporarily store internal names that will not necessarily be stored in the constant pool.
90       * This type table is used by the control flow and data flow analysis algorithm to compute stack map frames from
91       * scratch. This array associates to each index <code>i</code> the <code>TypeTableItem</code> whose index is
92       * <code>i</code>. All <code>TypeTableItem</code> objects stored in this array are also stored in the {@link #items}
93       * hash table. These two arrays allow to retrieve an <code>Item</code> from its index or, conversely, to get the
94       * index of an <code>Item</code> from its value. Each <code>TypeTableItem</code> stores an internal name in its
95       * {@link TypeTableItem#typeDesc} field.
96       */
97      private TypeTableItem[] typeTable;
98  
99      /**
100      * Number of elements in the {@link #typeTable} array.
101      */
102     private short typeCount;
103 
104     @NonNull
105     private final NormalTypeTableItem reusableNormalItem;
106     @NonNull
107     private final UninitializedTypeTableItem reusableUninitializedItem;
108     @NonNull
109     private final MergedTypeTableItem reusableMergedItem;
110 
111     @SuppressWarnings("OverlyCoupledMethod")
112     public ConstantPoolGeneration() {
113         pool = new ByteVector();
114         items = new Item[256];
115         // noinspection NumericCastThatLosesPrecision
116         threshold = (int) (0.75d * items.length);
117         index = 1;
118         reusableUTF8Item = new StringItem();
119         reusableStringItem = new StringItem();
120         reusableNameTypeItem = new NameAndTypeItem(0);
121         reusableClassMemberItem = new ClassMemberItem(0);
122         reusableIntItem = new IntItem(0);
123         reusableLongItem = new LongItem(0);
124         reusableFloatItem = new FloatItem(0);
125         reusableDoubleItem = new DoubleItem(0);
126         reusableMethodHandleItem = new MethodHandleItem(0);
127         reusableDynamicItem = new DynamicItem(0);
128         reusableNormalItem = new NormalTypeTableItem();
129         reusableUninitializedItem = new UninitializedTypeTableItem();
130         reusableMergedItem = new MergedTypeTableItem();
131     }
132 
133     /**
134      * Adds an UTF8 string to the constant pool of the class being built. Does nothing if the constant pool already
135      * contains a similar item.
136      *
137      * @param value
138      *            the String value.
139      *
140      * @return the index of a new or already existing UTF8 item.
141      */
142     @NonNegative
143     public int newUTF8(@NonNull String value) {
144         reusableUTF8Item.set(UTF8, value);
145 
146         StringItem result = get(reusableUTF8Item);
147 
148         if (result == null) {
149             pool.putByte(UTF8).putUTF8(value);
150 
151             result = new StringItem(index++, reusableUTF8Item);
152             put(result);
153         }
154 
155         return result.index;
156     }
157 
158     /**
159      * Adds a class reference to the constant pool of the class being built. Does nothing if the constant pool already
160      * contains a similar item.
161      *
162      * @param internalName
163      *            the internal name of the class.
164      *
165      * @return the index of a new or already existing class reference item.
166      */
167     @NonNegative
168     public int newClass(@NonNull String internalName) {
169         return newClassItem(internalName).index;
170     }
171 
172     /**
173      * Adds a class reference to the constant pool of the class being built. Does nothing if the constant pool already
174      * contains a similar item.
175      *
176      * @param internalName
177      *            the internal name of the class.
178      *
179      * @return a new or already existing class reference item.
180      */
181     @NonNull
182     public StringItem newClassItem(@NonNull String internalName) {
183         return newStringItem(CLASS, internalName);
184     }
185 
186     /**
187      * Adds a string to the constant pool of the class being built. Does nothing if the constant pool already contains a
188      * similar item.
189      *
190      * @param type
191      *            one of {@link ConstantPoolTypes#STRING}, {@link ConstantPoolTypes#CLASS} or
192      *            {@link ConstantPoolTypes#METHOD_TYPE}
193      * @param value
194      *            the String value.
195      *
196      * @return a new or already existing string item.
197      */
198     @NonNull
199     private StringItem newStringItem(int type, @NonNull String value) {
200         reusableStringItem.set(type, value);
201 
202         StringItem result = get(reusableStringItem);
203 
204         if (result == null) {
205             int itemIndex = newUTF8(value);
206             pool.put12(type, itemIndex);
207 
208             result = new StringItem(index++, reusableStringItem);
209             put(result);
210         }
211 
212         return result;
213     }
214 
215     /**
216      * Adds a method handle to the constant pool of the class being built. Does nothing if the constant pool already
217      * contains a similar item.
218      *
219      * @return a new or an already existing method type reference item.
220      */
221     @NonNull
222     public MethodHandleItem newMethodHandleItem(@NonNull MethodHandle methodHandle) {
223         reusableMethodHandleItem.set(methodHandle);
224 
225         MethodHandleItem result = get(reusableMethodHandleItem);
226 
227         if (result == null) {
228             int tag = methodHandle.tag;
229             int memberType = tag == MethodHandle.Tag.TAG_INVOKEINTERFACE ? IMETHOD_REF : METHOD_REF;
230             ClassMemberItem memberItem = newClassMemberItem(memberType, methodHandle.owner, methodHandle.name,
231                     methodHandle.desc);
232             pool.put11(METHOD_HANDLE, tag).putShort(memberItem.index);
233 
234             result = new MethodHandleItem(index++, reusableMethodHandleItem);
235             put(result);
236         }
237 
238         return result;
239     }
240 
241     @NonNull
242     private ClassMemberItem newClassMemberItem(int type, @NonNull String owner, @NonNull String name,
243             @NonNull String desc) {
244         reusableClassMemberItem.set(type, owner, name, desc);
245 
246         ClassMemberItem result = get(reusableClassMemberItem);
247 
248         if (result == null) {
249             int ownerItemIndex = newClass(owner);
250             int nameAndTypeItemIndex = newNameType(name, desc);
251             put122(type, ownerItemIndex, nameAndTypeItemIndex);
252 
253             result = new ClassMemberItem(index++, reusableClassMemberItem);
254             put(result);
255         }
256 
257         return result;
258     }
259 
260     /**
261      * Adds a field reference to the constant pool of the class being built. Does nothing if the constant pool already
262      * contains a similar item.
263      *
264      * @param owner
265      *            the internal name of the field's owner class
266      * @param name
267      *            the field's name
268      * @param desc
269      *            the field's descriptor
270      *
271      * @return a new or already existing field reference item
272      */
273     @NonNull
274     public ClassMemberItem newFieldItem(@NonNull String owner, @NonNull String name, @NonNull String desc) {
275         return newClassMemberItem(FIELD_REF, owner, name, desc);
276     }
277 
278     /**
279      * Adds a method reference to the constant pool of the class being built. Does nothing if the constant pool already
280      * contains a similar item.
281      *
282      * @param owner
283      *            the internal name of the method's owner class
284      * @param name
285      *            the method's name
286      * @param desc
287      *            the method's descriptor
288      * @param itf
289      *            <code>true</code> if <code>owner</code> is an interface
290      *
291      * @return a new or already existing method reference item
292      */
293     @NonNull
294     public ClassMemberItem newMethodItem(@NonNull String owner, @NonNull String name, @NonNull String desc,
295             boolean itf) {
296         return newClassMemberItem(itf ? IMETHOD_REF : METHOD_REF, owner, name, desc);
297     }
298 
299     /**
300      * Adds an integer to the constant pool of the class being built. Does nothing if the constant pool already contains
301      * a similar item.
302      *
303      * @param value
304      *            the int value
305      *
306      * @return a new or already existing int item
307      */
308     @NonNull
309     public IntItem newInteger(int value) {
310         reusableIntItem.setValue(value);
311 
312         IntItem result = get(reusableIntItem);
313 
314         if (result == null) {
315             pool.putByte(INTEGER).putInt(value);
316 
317             result = new IntItem(index++, reusableIntItem);
318             put(result);
319         }
320 
321         return result;
322     }
323 
324     /**
325      * Adds a float to the constant pool of the class being built. Does nothing if the constant pool already contains a
326      * similar item.
327      *
328      * @param value
329      *            the float value
330      *
331      * @return a new or already existing float item
332      */
333     @NonNull
334     public FloatItem newFloat(float value) {
335         reusableFloatItem.set(value);
336 
337         FloatItem result = get(reusableFloatItem);
338 
339         if (result == null) {
340             pool.putByte(FLOAT).putInt(reusableFloatItem.intVal);
341 
342             result = new FloatItem(index++, reusableFloatItem);
343             put(result);
344         }
345 
346         return result;
347     }
348 
349     /**
350      * Adds a long to the constant pool of the class being built. Does nothing if the constant pool already contains a
351      * similar item.
352      *
353      * @param value
354      *            the long value
355      *
356      * @return a new or already existing long item
357      */
358     @NonNull
359     public LongItem newLong(long value) {
360         reusableLongItem.setValue(value);
361 
362         LongItem result = get(reusableLongItem);
363 
364         if (result == null) {
365             pool.putByte(LONG).putLong(value);
366 
367             result = new LongItem(index, reusableLongItem);
368             index += 2;
369             put(result);
370         }
371 
372         return result;
373     }
374 
375     /**
376      * Adds a double to the constant pool of the class being built. Does nothing if the constant pool already contains a
377      * similar item.
378      *
379      * @param value
380      *            the double value
381      *
382      * @return a new or already existing double item
383      */
384     @NonNull
385     public DoubleItem newDouble(double value) {
386         reusableDoubleItem.set(value);
387 
388         DoubleItem result = get(reusableDoubleItem);
389 
390         if (result == null) {
391             pool.putByte(DOUBLE).putLong(reusableDoubleItem.longVal);
392 
393             result = new DoubleItem(index, reusableDoubleItem);
394             index += 2;
395             put(result);
396         }
397 
398         return result;
399     }
400 
401     /**
402      * Adds a name and type to the constant pool of the class being built. Does nothing if the constant pool already
403      * contains a similar item.
404      *
405      * @param name
406      *            a name
407      * @param desc
408      *            a type descriptor
409      *
410      * @return the index of a new or already existing name and type item
411      */
412     @NonNegative
413     private int newNameType(@NonNull String name, @NonNull String desc) {
414         reusableNameTypeItem.set(name, desc);
415 
416         NameAndTypeItem result = get(reusableNameTypeItem);
417 
418         if (result == null) {
419             int nameItemIndex = newUTF8(name);
420             int descItemIndex = newUTF8(desc);
421             put122(NAME_TYPE, nameItemIndex, descItemIndex);
422 
423             result = new NameAndTypeItem(index++, reusableNameTypeItem);
424             put(result);
425         }
426 
427         return result.index;
428     }
429 
430     /**
431      * Adds a number or string constant to the constant pool of the class being built. Does nothing if the constant pool
432      * already contains a similar item.
433      *
434      * @param cst
435      *            the value of the constant to be added to the constant pool, which must be an {@link Integer}, a
436      *            {@link Float}, a {@link Long}, a {@link Double}, a {@link String}, or a {@link JavaType}
437      *
438      * @return a new or already existing constant item with the given value
439      */
440     @NonNull
441     public Item newConstItem(@NonNull Object cst) {
442         if (cst instanceof String) {
443             return newStringItem(STRING, (String) cst);
444         }
445 
446         if (cst instanceof Number) {
447             return newNumberItem((Number) cst);
448         }
449 
450         if (cst instanceof Character) {
451             return newInteger((Character) cst);
452         }
453 
454         if (cst instanceof Boolean) {
455             int val = (boolean) cst ? 1 : 0;
456             return newInteger(val);
457         }
458 
459         if (cst instanceof ReferenceType) {
460             String typeDesc = ((ReferenceType) cst).getInternalName();
461             return cst instanceof MethodType ? newStringItem(METHOD_TYPE, typeDesc) : newClassItem(typeDesc);
462         }
463 
464         if (cst instanceof PrimitiveType) {
465             String typeDesc = ((PrimitiveType) cst).getDescriptor();
466             return newClassItem(typeDesc);
467         }
468 
469         if (cst instanceof MethodHandle) {
470             return newMethodHandleItem((MethodHandle) cst);
471         }
472 
473         if (cst instanceof DynamicItem) {
474             DynamicItem dynamicItem = (DynamicItem) cst;
475             return createDynamicItem(dynamicItem.type, dynamicItem.name, dynamicItem.desc, dynamicItem.bsmIndex);
476         }
477         throw new IllegalArgumentException("value " + cst);
478     }
479 
480     @NonNull
481     private Item newNumberItem(@NonNull Number cst) {
482         if (cst instanceof Float) {
483             return newFloat(cst.floatValue());
484         }
485 
486         if (cst instanceof Long) {
487             return newLong(cst.longValue());
488         }
489 
490         if (cst instanceof Double) {
491             return newDouble(cst.doubleValue());
492         }
493 
494         return newInteger(cst.intValue());
495     }
496 
497     /**
498      * Adds the given internal name to {@link #typeTable} and returns its index. Does nothing if the type table already
499      * contains this internal name.
500      *
501      * @param type
502      *            the internal name to be added to the type table
503      *
504      * @return the index of this internal name in the type table
505      */
506     @NonNegative
507     public int addNormalType(@NonNull String type) {
508         reusableNormalItem.set(type);
509 
510         TypeTableItem result = get(reusableNormalItem);
511 
512         if (result == null) {
513             result = new NormalTypeTableItem(++typeCount, reusableNormalItem);
514             addToTypeTable(result);
515         }
516 
517         return result.index;
518     }
519 
520     /**
521      * Adds the given "uninitialized" type to {@link #typeTable} and returns its index. This method is used for
522      * UNINITIALIZED types, made of an internal name and a bytecode offset.
523      *
524      * @param type
525      *            the internal name to be added to the type table
526      * @param offset
527      *            the bytecode offset of the NEW instruction that created this UNINITIALIZED type value
528      *
529      * @return the index of this internal name in the type table
530      */
531     @NonNegative
532     public int addUninitializedType(@NonNull String type, @NonNegative int offset) {
533         reusableUninitializedItem.set(type, offset);
534 
535         TypeTableItem result = get(reusableUninitializedItem);
536 
537         if (result == null) {
538             result = new UninitializedTypeTableItem(++typeCount, reusableUninitializedItem);
539             addToTypeTable(result);
540         }
541 
542         return result.index;
543     }
544 
545     private void addToTypeTable(@NonNull TypeTableItem newItem) {
546         put(newItem);
547 
548         if (typeTable == null) {
549             typeTable = new TypeTableItem[16];
550         }
551 
552         int newItemIndex = typeCount;
553         enlargeTypeTableIfNeeded(newItemIndex);
554         typeTable[newItemIndex] = newItem;
555     }
556 
557     private void enlargeTypeTableIfNeeded(@NonNegative int newItemIndex) {
558         int currentTypeCount = typeTable.length;
559 
560         if (newItemIndex == currentTypeCount) {
561             TypeTableItem[] newTable = new TypeTableItem[2 * currentTypeCount];
562             System.arraycopy(typeTable, 0, newTable, 0, currentTypeCount);
563             typeTable = newTable;
564         }
565     }
566 
567     /**
568      * Returns the index of the common super type of the two given types. This method calls {@link #getCommonSuperClass}
569      * and caches the result in the {@link #items} hash table to speedup future calls with the same parameters.
570      *
571      * @param type1
572      *            index of an internal name in {@link #typeTable}
573      * @param type2
574      *            index of an internal name in {@link #typeTable}
575      *
576      * @return the index of the common super type of the two given types
577      */
578     @NonNegative
579     public int getMergedType(@NonNegative int type1, @NonNegative int type2) {
580         reusableMergedItem.set(type1, type2);
581 
582         MergedTypeTableItem result = get(reusableMergedItem);
583 
584         if (result == null) {
585             String type1Desc = getInternalName(type1);
586             String type2Desc = getInternalName(type2);
587             String commonSuperClass = getCommonSuperClass(type1Desc, type2Desc);
588             reusableMergedItem.commonSuperTypeIndex = addNormalType(commonSuperClass);
589 
590             result = new MergedTypeTableItem(reusableMergedItem);
591             put(result);
592         }
593 
594         return result.commonSuperTypeIndex;
595     }
596 
597     /**
598      * Returns the common super type of the two given types. The default implementation of this method <i>loads</i> the
599      * two given classes and uses the java.lang.Class methods to find the common super class. It can be overridden to
600      * compute this common super type in other ways, in particular without actually loading any class, or to take into
601      * account the class that is currently being generated by this ClassWriter, which can of course not be loaded since
602      * it is under construction.
603      *
604      * @param type1
605      *            the internal name of a class
606      * @param type2
607      *            the internal name of another class
608      *
609      * @return the internal name of the common super class of the two given classes
610      */
611     @NonNull
612     private static String getCommonSuperClass(@NonNull String type1, @NonNull String type2) {
613         // Reimplemented to avoid "duplicate class definition" errors.
614         String class1 = type1;
615         String class2 = type2;
616 
617         while (true) {
618             if (OBJECT.equals(class1) || OBJECT.equals(class2)) {
619                 return OBJECT;
620             }
621 
622             String superClass = ClassLoad.whichIsSuperClass(class1, class2);
623 
624             if (superClass != null) {
625                 return superClass;
626             }
627 
628             class1 = ClassLoad.getSuperClass(class1);
629             class2 = ClassLoad.getSuperClass(class2);
630 
631             if (class1.equals(class2)) {
632                 return class1;
633             }
634         }
635     }
636 
637     @NonNull
638     public String getInternalName(@NonNegative int typeTableIndex) {
639         TypeTableItem typeTableItem = typeTable[typeTableIndex]; // Normal or Uninitialized
640         return typeTableItem.typeDesc;
641     }
642 
643     @NonNull
644     public UninitializedTypeTableItem getUninitializedItemValue(@NonNegative int typeTableIndex) {
645         return (UninitializedTypeTableItem) typeTable[typeTableIndex];
646     }
647 
648     @Nullable
649     public Item getItem(@NonNegative int itemHashCode) {
650         return items[itemHashCode % items.length];
651     }
652 
653     /**
654      * Returns the constant pool's hash table item which is equal to the given item.
655      *
656      * @param key
657      *            a constant pool item
658      *
659      * @return the constant pool's hash table item which is equal to the given item, or <code>null</code> if there is no
660      *         such item
661      */
662     @Nullable
663     private <I extends Item> I get(@NonNull I key) {
664         Item item = getItem(key.getHashCode());
665         int keyType = key.getType();
666 
667         while (item != null && (item.getType() != keyType || !key.isEqualTo(item))) {
668             item = item.getNext();
669         }
670 
671         // noinspection unchecked
672         return (I) item;
673     }
674 
675     /**
676      * Puts the given item in the constant pool's hash table. The hash table <i>must</i> not already contains this item.
677      *
678      * @param item
679      *            the item to be added to the constant pool's hash table
680      */
681     private void put(@NonNull Item item) {
682         resizeItemArrayIfNeeded();
683         item.setNext(items);
684     }
685 
686     private void resizeItemArrayIfNeeded() {
687         if (index + typeCount > threshold) {
688             int ll = items.length;
689             int nl = ll * 2 + 1;
690             Item[] newItems = new Item[nl];
691 
692             for (int l = ll - 1; l >= 0; l--) {
693                 Item j = items[l];
694                 put(newItems, j);
695             }
696 
697             items = newItems;
698             // noinspection NumericCastThatLosesPrecision
699             threshold = (int) (nl * 0.75);
700         }
701     }
702 
703     private static void put(@NonNull Item[] newItems, @Nullable Item item) {
704         while (item != null) {
705             Item next = item.getNext();
706             item.setNext(newItems);
707             // noinspection AssignmentToMethodParameter
708             item = next;
709         }
710     }
711 
712     /**
713      * Puts one byte and two shorts into the constant pool.
714      */
715     private void put122(int b, int s1, int s2) {
716         pool.put12(b, s1).putShort(s2);
717     }
718 
719     @NonNegative
720     public int getSize() {
721         return pool.getLength();
722     }
723 
724     public void checkConstantPoolMaxSize() {
725         if (index > 0xFFFF) {
726             throw new RuntimeException("Class file too large!");
727         }
728     }
729 
730     public void put(@NonNull ByteVector out) {
731         out.putShort(index).putByteVector(pool);
732     }
733 
734     public void copy(@NonNull byte[] code, @NonNegative int off, @NonNegative int header, @NonNull Item[] cpItems) {
735         pool.putByteArray(code, off, header - off);
736         items = cpItems;
737 
738         int ll = cpItems.length;
739         // noinspection NumericCastThatLosesPrecision
740         threshold = (int) (0.75d * ll);
741         index = ll;
742     }
743 
744     @NonNull
745     public DynamicItem createDynamicItem(int type, @NonNull String name, @NonNull String desc,
746             @NonNegative int bsmIndex) {
747         reusableDynamicItem.set(type, name, desc, bsmIndex);
748 
749         DynamicItem result = get(reusableDynamicItem);
750 
751         if (result == null) {
752             int nameAndTypeItemIndex = newNameType(name, desc);
753             put122(type, bsmIndex, nameAndTypeItemIndex);
754 
755             result = new DynamicItem(index++, reusableDynamicItem);
756             put(result);
757         }
758 
759         return result;
760     }
761 }