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.annotations;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.lang.reflect.Array;
12  
13  import mockit.asm.constantPool.ConstantPoolGeneration;
14  import mockit.asm.constantPool.Item;
15  import mockit.asm.types.JavaType;
16  import mockit.asm.util.ByteVector;
17  
18  import org.checkerframework.checker.index.qual.NonNegative;
19  
20  /**
21   * A visitor to visit a Java annotation, in the following order: (<code>visit</code> | <code>visitEnum</code> |
22   * <code>visitAnnotation</code> | <code>visitArray</code>)* <code>visitEnd</code>.
23   */
24  public final class AnnotationVisitor {
25      /**
26       * The constant pool to which this annotation must be added.
27       */
28      @NonNull
29      private final ConstantPoolGeneration cp;
30  
31      /**
32       * The number of attribute values in this annotation.
33       */
34      @NonNegative
35      private int attributeCount;
36  
37      /**
38       * <code>true<code> if values are named, <code>false</code> otherwise. Annotation writers used for annotation
39       * default and annotation arrays use unnamed values.
40       */
41      private final boolean named;
42  
43      /**
44       * The annotation values in bytecode form. This byte vector only contains the values themselves, i.e. the number of
45       * values must be stored as an unsigned short just before these bytes.
46       */
47      @NonNull
48      private final ByteVector bv;
49  
50      /**
51       * Where the number of values of this annotation must be stored in {@link #bv}.
52       */
53      @NonNegative
54      private final int offset;
55  
56      /**
57       * Next annotation visitor. This field is used to store annotation lists.
58       */
59      @Nullable
60      private AnnotationVisitor next;
61  
62      /**
63       * Previous annotation visitor. This field is used to store annotation lists.
64       */
65      @Nullable
66      private AnnotationVisitor prev;
67  
68      public AnnotationVisitor(@NonNull ConstantPoolGeneration cp, @NonNull String typeDesc) {
69          this.cp = cp;
70          named = true;
71          bv = new ByteVector();
72          bv.putShort(cp.newUTF8(typeDesc));
73          bv.putShort(0); // reserve space for value count
74          offset = 2;
75      }
76  
77      private AnnotationVisitor(@NonNull AnnotationVisitor parent, boolean named) {
78          cp = parent.cp;
79          this.named = named;
80          bv = parent.bv;
81          offset = getByteLength() - 2;
82      }
83  
84      @NonNegative
85      private int getByteLength() {
86          return bv.getLength();
87      }
88  
89      /**
90       * Sets the visitor to the {@link #next} annotation.
91       */
92      public void setNext(@Nullable AnnotationVisitor next) {
93          this.next = next;
94      }
95  
96      /**
97       * Visits a primitive, String, Class, or array value of the annotation.
98       *
99       * @param name
100      *            the value name
101      * @param value
102      *            the actual value, whose type must be {@link Byte}, {@link Boolean}, {@link Character}, {@link Short},
103      *            {@link Integer}, {@link Long}, {@link Float}, {@link Double}, {@link String}, or {@link JavaType} of
104      *            OBJECT or ARRAY sort; this value can also be an array of byte, boolean, short, char, int, long, float
105      *            or double values (this is equivalent to using {@link #visitArray} and visiting each array element in
106      *            turn, but is more convenient)
107      */
108     void visit(@Nullable String name, @NonNull Object value) {
109         putName(name);
110 
111         if (value instanceof String) {
112             putString('s', (String) value);
113         } else if (putValueWhenPrimitive(value)) {
114             // OK
115         } else if (value instanceof JavaType) {
116             putType((JavaType) value);
117         } else {
118             putElementValuesWhenArray(value);
119         }
120     }
121 
122     private void putName(@Nullable String name) {
123         attributeCount++;
124 
125         if (named) {
126             // noinspection ConstantConditions
127             putString(name);
128         }
129     }
130 
131     private boolean putValueWhenPrimitive(@NonNull Object value) {
132         if (value instanceof Boolean) {
133             putBoolean((Boolean) value);
134         } else if (value instanceof Integer) {
135             putInteger('I', (Integer) value);
136         } else if (value instanceof Double) {
137             putDouble((Double) value);
138         } else if (value instanceof Float) {
139             putFloat((Float) value);
140         } else if (value instanceof Long) {
141             putLong((Long) value);
142         } else if (value instanceof Byte) {
143             putInteger('B', (Byte) value);
144         } else if (value instanceof Character) {
145             putInteger('C', (Character) value);
146         } else if (value instanceof Short) {
147             putInteger('S', (Short) value);
148         } else {
149             return false;
150         }
151 
152         return true;
153     }
154 
155     private void putItem(int typeCode, @NonNull Item item) {
156         bv.put12(typeCode, item.index);
157     }
158 
159     private void putBoolean(boolean value) {
160         putInteger('Z', value ? 1 : 0);
161     }
162 
163     private void putInteger(int typeCode, int value) {
164         Item item = cp.newInteger(value);
165         putItem(typeCode, item);
166     }
167 
168     private void putDouble(double value) {
169         Item item = cp.newDouble(value);
170         putItem('D', item);
171     }
172 
173     private void putFloat(float value) {
174         Item item = cp.newFloat(value);
175         putItem('F', item);
176     }
177 
178     private void putLong(long value) {
179         Item item = cp.newLong(value);
180         putItem('J', item);
181     }
182 
183     private void putType(@NonNull JavaType type) {
184         String typeDescriptor = type.getDescriptor();
185         putString('c', typeDescriptor);
186     }
187 
188     private void putString(int b, @NonNull String value) {
189         int itemIndex = cp.newUTF8(value);
190         bv.put12(b, itemIndex);
191     }
192 
193     private void putString(@NonNull String value) {
194         int itemIndex = cp.newUTF8(value);
195         bv.putShort(itemIndex);
196     }
197 
198     private void putArrayLength(@NonNegative int length) {
199         bv.put12('[', length);
200     }
201 
202     private void putElementValuesWhenArray(@NonNull Object value) {
203         if (value instanceof byte[]) {
204             putArrayElementValues('B', value);
205         } else if (value instanceof boolean[]) {
206             putArrayElementValues('Z', value);
207         } else if (value instanceof short[]) {
208             putArrayElementValues('S', value);
209         } else if (value instanceof char[]) {
210             putArrayElementValues('C', value);
211         } else if (value instanceof int[]) {
212             putArrayElementValues('I', value);
213         } else if (value instanceof long[]) {
214             putArrayElementValues('J', value);
215         } else if (value instanceof float[]) {
216             putArrayElementValues('F', value);
217         } else if (value instanceof double[]) {
218             putArrayElementValues('D', value);
219         }
220     }
221 
222     private void putArrayElementValues(char elementType, @NonNull Object array) {
223         int length = Array.getLength(array);
224         putArrayLength(length);
225 
226         for (int i = 0; i < length; i++) {
227             switch (elementType) {
228                 case 'J': {
229                     long value = Array.getLong(array, i);
230                     putLong(value);
231                     break;
232                 }
233                 case 'F': {
234                     float value = Array.getFloat(array, i);
235                     putFloat(value);
236                     break;
237                 }
238                 case 'D': {
239                     double value = Array.getDouble(array, i);
240                     putDouble(value);
241                     break;
242                 }
243                 case 'Z': {
244                     boolean value = Array.getBoolean(array, i);
245                     putBoolean(value);
246                     break;
247                 }
248                 default: {
249                     int value = Array.getInt(array, i);
250                     putInteger(elementType, value);
251                     break;
252                 }
253             }
254         }
255     }
256 
257     /**
258      * Visits an enumeration value of the annotation.
259      *
260      * @param name
261      *            the value name
262      * @param desc
263      *            the class descriptor of the enumeration class
264      * @param value
265      *            the actual enumeration value
266      */
267     void visitEnum(@Nullable String name, @NonNull String desc, @NonNull String value) {
268         putName(name);
269         putString('e', desc);
270         putString(value);
271     }
272 
273     /**
274      * Visits a nested annotation value of the annotation.
275      *
276      * @param name
277      *            the value name
278      * @param desc
279      *            the class descriptor of the nested annotation class
280      *
281      * @return a visitor to visit the actual nested annotation value
282      */
283     @NonNull
284     AnnotationVisitor visitAnnotation(@Nullable String name, @NonNull String desc) {
285         putName(name);
286 
287         // Write tag and type, and reserve space for value count.
288         putString('@', desc);
289         bv.putShort(0);
290 
291         return new AnnotationVisitor(this, true);
292     }
293 
294     /**
295      * Visits an array value of the annotation. Note that arrays of primitive types can be passed as value to
296      * {@link #visit(String, Object)}.
297      *
298      * @param name
299      *            the value name
300      *
301      * @return a visitor to visit the actual array value elements; the 'name' parameters passed to the methods of this
302      *         visitor are ignored
303      */
304     @NonNull
305     AnnotationVisitor visitArray(@Nullable String name) {
306         putName(name);
307 
308         // Write tag, and reserve space for array size.
309         putArrayLength(0);
310 
311         return new AnnotationVisitor(this, false);
312     }
313 
314     /**
315      * Visits the end of the annotation.
316      */
317     @SuppressWarnings("NumericCastThatLosesPrecision")
318     void visitEnd() {
319         byte[] data = bv.getData();
320         data[offset] = (byte) (attributeCount >>> 8);
321         data[offset + 1] = (byte) attributeCount;
322     }
323 
324     /**
325      * Returns the size of this annotation list.
326      */
327     @NonNegative
328     public int getSize() {
329         int size = 0;
330         AnnotationVisitor annotation = this;
331 
332         while (annotation != null) {
333             size += annotation.getByteLength();
334             annotation = annotation.next;
335         }
336 
337         return size;
338     }
339 
340     /**
341      * Puts the annotations of this annotation writer list into the given byte vector.
342      */
343     public void put(@NonNull ByteVector out) {
344         AnnotationVisitor aw = this;
345         AnnotationVisitor last = null;
346         int n = 0;
347         int size = 2;
348 
349         while (aw != null) {
350             n++;
351             size += aw.getByteLength();
352             aw.prev = last;
353             last = aw;
354             aw = aw.next;
355         }
356 
357         out.putInt(size);
358         out.putShort(n);
359         putFromLastToFirst(out, last);
360     }
361 
362     private static void putFromLastToFirst(@NonNull ByteVector out, @Nullable AnnotationVisitor aw) {
363         while (aw != null) {
364             out.putByteVector(aw.bv);
365             aw = aw.prev;
366         }
367     }
368 
369     /**
370      * Puts the given annotation lists into the given byte vector.
371      */
372     public static void put(@NonNull ByteVector out, @NonNull AnnotationVisitor[] anns) {
373         putNumberAndSizeOfAnnotations(out, anns);
374 
375         for (AnnotationVisitor ann : anns) {
376             AnnotationVisitor last = putNumberOfAnnotations(out, ann);
377             putFromLastToFirst(out, last);
378         }
379     }
380 
381     private static void putNumberAndSizeOfAnnotations(@NonNull ByteVector out, @NonNull AnnotationVisitor[] anns) {
382         int numAnns = anns.length;
383         int size = 1 + 2 * numAnns;
384 
385         for (AnnotationVisitor aw : anns) {
386             if (aw != null) {
387                 size += aw.getSize();
388             }
389         }
390 
391         out.putInt(size).putByte(numAnns);
392     }
393 
394     @Nullable
395     private static AnnotationVisitor putNumberOfAnnotations(@NonNull ByteVector out, @Nullable AnnotationVisitor aw) {
396         AnnotationVisitor last = null;
397         int n = 0;
398 
399         while (aw != null) {
400             n++;
401             aw.prev = last;
402             last = aw;
403             aw = aw.next;
404         }
405 
406         out.putShort(n);
407         return last;
408     }
409 }