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.types.JavaType;
14  import mockit.asm.types.PrimitiveType;
15  import mockit.asm.util.BytecodeReader;
16  
17  import org.checkerframework.checker.index.qual.NonNegative;
18  
19  public final class AnnotationReader extends BytecodeReader {
20      public AnnotationReader(@NonNull BytecodeReader br) {
21          super(br);
22      }
23  
24      /**
25       * Reads the values of a named annotation and makes the given visitor visit them.
26       *
27       * @param startingCodeIndex
28       *            the start offset in {@link #code} of the values to be read (including the unsigned short that gives
29       *            the number of values)
30       * @param av
31       *            the visitor that must visit the values
32       *
33       * @return the end offset of the annotation values
34       */
35      @NonNegative
36      public int readNamedAnnotationValues(@NonNegative int startingCodeIndex, @Nullable AnnotationVisitor av) {
37          codeIndex = startingCodeIndex;
38          readAnnotationValues(true, av);
39          return codeIndex;
40      }
41  
42      private void readAnnotationValues(boolean named, @Nullable AnnotationVisitor av) {
43          int valueCount = readUnsignedShort();
44          readAnnotationValues(valueCount, named, av);
45      }
46  
47      private void readAnnotationValues(@NonNegative int valueCount, boolean named, @Nullable AnnotationVisitor av) {
48          while (valueCount > 0) {
49              String name = named ? readNonnullUTF8() : null;
50              readAnnotationValue(name, av);
51              valueCount--;
52          }
53  
54          if (av != null) {
55              av.visitEnd();
56          }
57      }
58  
59      private void readAnnotationValue(@Nullable String name, @Nullable AnnotationVisitor av) {
60          int typeCode = readUnsignedByte();
61  
62          if (av == null) {
63              readAnnotationValue(typeCode);
64          } else {
65              Object value = readAnnotationValueIfPrimitiveOrString(typeCode);
66  
67              if (value != null) {
68                  av.visit(name, value);
69              } else {
70                  // noinspection SwitchStatementWithoutDefaultBranch
71                  switch (typeCode) {
72                      case 'e':
73                          readEnumConstValue(name, av);
74                          break; // enum_const_value
75                      case 'c':
76                          readClassInfo(name, av);
77                          break; // class_info
78                      case '@':
79                          readNestedAnnotation(name, av);
80                          break; // annotation_value
81                      case '[':
82                          readArrayValue(name, av); // array_value
83                  }
84              }
85          }
86      }
87  
88      private void readAnnotationValue(@NonNegative int typeCode) {
89          switch (typeCode) {
90              case 'e':
91                  codeIndex += 4;
92                  break; // enum_const_value
93              case '@':
94                  codeIndex += 2;
95                  readAnnotationValues(true, null);
96                  break; // annotation_value
97              case '[':
98                  readAnnotationValues(false, null);
99                  break;
100             default:
101                 codeIndex += 2;
102         }
103     }
104 
105     @Nullable
106     @SuppressWarnings({ "NumericCastThatLosesPrecision", "SwitchStatementWithoutDefaultBranch" })
107     private Object readAnnotationValueIfPrimitiveOrString(@NonNegative int typeCode) {
108         switch (typeCode) {
109             case 'I':
110             case 'J':
111             case 'F':
112             case 'D':
113                 return readConstItem(); // CONSTANT_Integer/Long/Float/Double
114             case 'B':
115                 return (byte) readValueOfOneOrTwoBytes(); // CONSTANT_Byte
116             case 'Z':
117                 return readValueOfOneOrTwoBytes() != 0; // CONSTANT_Boolean
118             case 'S':
119                 return (short) readValueOfOneOrTwoBytes(); // CONSTANT_Short
120             case 'C':
121                 return (char) readValueOfOneOrTwoBytes(); // CONSTANT_Char
122             case 's':
123                 return readNonnullUTF8(); // CONSTANT_Utf8
124         }
125 
126         return null;
127     }
128 
129     private int readValueOfOneOrTwoBytes() {
130         int itemIndex = readUnsignedShort();
131         int valueCodeIndex = items[itemIndex];
132         return readInt(valueCodeIndex);
133     }
134 
135     private void readEnumConstValue(@Nullable String name, @NonNull AnnotationVisitor av) {
136         String enumDesc = readNonnullUTF8();
137         String enumValue = readNonnullUTF8();
138         av.visitEnum(name, enumDesc, enumValue);
139     }
140 
141     private void readClassInfo(@Nullable String name, @NonNull AnnotationVisitor av) {
142         String typeDesc = readNonnullUTF8();
143         JavaType value = JavaType.getType(typeDesc);
144         av.visit(name, value);
145     }
146 
147     private void readNestedAnnotation(@Nullable String name, @NonNull AnnotationVisitor av) {
148         String desc = readNonnullUTF8();
149         AnnotationVisitor nestedVisitor = av.visitAnnotation(name, desc);
150         readAnnotationValues(true, nestedVisitor);
151     }
152 
153     private void readArrayValue(@Nullable String name, @NonNull AnnotationVisitor av) {
154         int valueCount = readUnsignedShort();
155 
156         if (valueCount == 0) {
157             AnnotationVisitor arrayVisitor = av.visitArray(name);
158             arrayVisitor.visitEnd();
159             return;
160         }
161 
162         int typeCode = readUnsignedByte();
163         PrimitiveType primitiveElementType = PrimitiveType.getPrimitiveType(typeCode);
164 
165         if (primitiveElementType == null) {
166             AnnotationVisitor arrayVisitor = av.visitArray(name);
167             codeIndex--;
168             readAnnotationValues(valueCount, false, arrayVisitor);
169             return;
170         }
171 
172         Class<?> elementType = primitiveElementType.getType();
173         Object array = Array.newInstance(elementType, valueCount);
174         fillArrayElements(valueCount, typeCode, array);
175         av.visit(name, array);
176         codeIndex--;
177     }
178 
179     private void fillArrayElements(@NonNegative int length, @NonNegative int typeCode, @NonNull Object array) {
180         for (int i = 0; i < length; i++) {
181             int itemIndex = readUnsignedShort();
182             int index = items[itemIndex];
183             Object value = getArrayElementValue(typeCode, index);
184             Array.set(array, i, value);
185             codeIndex++;
186         }
187     }
188 
189     @NonNull
190     private Object getArrayElementValue(@NonNegative int typeCode, @NonNegative int valueCodeIndex) {
191         switch (typeCode) {
192             case 'Z':
193                 return readBoolean(valueCodeIndex);
194             case 'C':
195                 return readChar(valueCodeIndex);
196             case 'B':
197                 return readUnsignedByte(valueCodeIndex);
198             case 'S':
199                 return readShort(valueCodeIndex);
200             case 'F':
201                 return readFloat(valueCodeIndex);
202             case 'D':
203                 return readDouble(valueCodeIndex);
204             case 'J':
205                 return readLong(valueCodeIndex);
206             default:
207                 return readInt(valueCodeIndex);
208         }
209     }
210 }