1 /*******************************************************************************
2 * Copyright (c) 2005, 2016 BEA Systems, Inc.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * tyeung@bea.com - initial API and implementation
13 * olivier_thomann@ca.ibm.com - add hashCode() and equals(..) methods
14 *******************************************************************************/
15 package org.eclipse.jdt.internal.compiler.classfmt;
16
17 import java.util.Arrays;
18
19 import org.eclipse.jdt.core.compiler.CharOperation;
20 import org.eclipse.jdt.internal.compiler.ast.Annotation;
21 import org.eclipse.jdt.internal.compiler.codegen.ConstantPool;
22 import org.eclipse.jdt.internal.compiler.env.*;
23 import org.eclipse.jdt.internal.compiler.impl.*;
24 import org.eclipse.jdt.internal.compiler.lookup.TagBits;
25 import org.eclipse.jdt.internal.compiler.util.Util;
26
27 public class AnnotationInfo extends ClassFileStruct implements IBinaryAnnotation {
28 /** The name of the annotation type */
29 private char[] typename;
30 /**
31 * null until this annotation is initialized
32 * @see #getElementValuePairs()
33 */
34 private volatile ElementValuePairInfo[] pairs;
35
36 long standardAnnotationTagBits = 0;
37 int readOffset = 0;
38
39 static Object[] EmptyValueArray = new Object[0];
40
41 public RuntimeException exceptionDuringDecode;
42
AnnotationInfo(byte[] classFileBytes, int[] contantPoolOffsets, int offset)43 AnnotationInfo(byte[] classFileBytes, int[] contantPoolOffsets, int offset) {
44 super(classFileBytes, contantPoolOffsets, offset);
45 }
46 /**
47 * @param classFileBytes
48 * @param offset the offset into <code>classFileBytes</code> for the "type_index" of the annotation attribute.
49 * @param populate <code>true</code> to indicate to build out the annotation structure.
50 */
AnnotationInfo(byte[] classFileBytes, int[] contantPoolOffsets, int offset, boolean runtimeVisible, boolean populate)51 AnnotationInfo(byte[] classFileBytes, int[] contantPoolOffsets, int offset, boolean runtimeVisible, boolean populate) {
52 this(classFileBytes, contantPoolOffsets, offset);
53 if (populate)
54 decodeAnnotation();
55 else
56 this.readOffset = scanAnnotation(0, runtimeVisible, true);
57 }
decodeAnnotation()58 private void decodeAnnotation() {
59 this.readOffset = 0;
60 int utf8Offset = this.constantPoolOffsets[u2At(0)] - this.structOffset;
61 this.typename = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
62 int numberOfPairs = u2At(2);
63 // u2 type_index + u2 num_member_value_pair
64 this.readOffset += 4;
65 ElementValuePairInfo[] decodedPairs = numberOfPairs == 0 ? ElementValuePairInfo.NoMembers : new ElementValuePairInfo[numberOfPairs];
66 int i = 0;
67 try {
68 while (i < numberOfPairs) {
69 // u2 member_name_index;
70 utf8Offset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
71 char[] membername = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
72 this.readOffset += 2;
73 Object value = decodeDefaultValue();
74 decodedPairs[i++] = new ElementValuePairInfo(membername, value);
75 }
76 this.pairs = decodedPairs;
77 } catch (RuntimeException any) {
78 sanitizePairs(decodedPairs);
79 StringBuilder newMessage = new StringBuilder(any.getMessage());
80 newMessage.append(" while decoding pair #").append(i).append(" of annotation @").append(this.typename); //$NON-NLS-1$ //$NON-NLS-2$
81 newMessage.append(", bytes at structOffset ").append(this.structOffset).append(":"); //$NON-NLS-1$ //$NON-NLS-2$
82 int offset = this.structOffset;
83 while (offset <= this.structOffset+this.readOffset && offset < this.reference.length) {
84 newMessage.append(' ').append(Integer.toHexString(this.reference[offset++] & 0xFF));
85 }
86 throw new IllegalStateException(newMessage.toString(), any);
87 }
88 }
sanitizePairs(ElementValuePairInfo[] oldPairs)89 private void sanitizePairs(ElementValuePairInfo[] oldPairs) {
90 if (oldPairs != null) {
91 ElementValuePairInfo[] newPairs = new ElementValuePairInfo[oldPairs.length];
92 int count = 0;
93 for (int i = 0; i < oldPairs.length; i++) {
94 ElementValuePairInfo evpInfo = oldPairs[i];
95 if (evpInfo != null)
96 newPairs[count++] = evpInfo;
97 }
98 if (count < oldPairs.length) {
99 this.pairs = Arrays.copyOf(newPairs, count);
100 } else {
101 this.pairs = newPairs;
102 }
103 } else {
104 this.pairs = ElementValuePairInfo.NoMembers;
105 }
106 }
decodeDefaultValue()107 Object decodeDefaultValue() {
108 Object value = null;
109 // u1 tag;
110 int tag = u1At(this.readOffset);
111 this.readOffset++;
112 int constValueOffset = -1;
113 switch (tag) {
114 case 'Z': // boolean constant
115 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
116 value = BooleanConstant.fromValue(i4At(constValueOffset + 1) == 1);
117 this.readOffset += 2;
118 break;
119 case 'I': // integer constant
120 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
121 value = IntConstant.fromValue(i4At(constValueOffset + 1));
122 this.readOffset += 2;
123 break;
124 case 'C': // char constant
125 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
126 value = CharConstant.fromValue((char) i4At(constValueOffset + 1));
127 this.readOffset += 2;
128 break;
129 case 'B': // byte constant
130 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
131 value = ByteConstant.fromValue((byte) i4At(constValueOffset + 1));
132 this.readOffset += 2;
133 break;
134 case 'S': // short constant
135 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
136 value = ShortConstant.fromValue((short) i4At(constValueOffset + 1));
137 this.readOffset += 2;
138 break;
139 case 'D': // double constant
140 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
141 value = DoubleConstant.fromValue(doubleAt(constValueOffset + 1));
142 this.readOffset += 2;
143 break;
144 case 'F': // float constant
145 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
146 value = FloatConstant.fromValue(floatAt(constValueOffset + 1));
147 this.readOffset += 2;
148 break;
149 case 'J': // long constant
150 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
151 value = LongConstant.fromValue(i8At(constValueOffset + 1));
152 this.readOffset += 2;
153 break;
154 case 's': // String
155 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
156 value = StringConstant.fromValue(String.valueOf(utf8At(constValueOffset + 3, u2At(constValueOffset + 1))));
157 this.readOffset += 2;
158 break;
159 case 'e':
160 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
161 char[] typeName = utf8At(constValueOffset + 3, u2At(constValueOffset + 1));
162 this.readOffset += 2;
163 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
164 char[] constName = utf8At(constValueOffset + 3, u2At(constValueOffset + 1));
165 this.readOffset += 2;
166 value = new EnumConstantSignature(typeName, constName);
167 break;
168 case 'c':
169 constValueOffset = this.constantPoolOffsets[u2At(this.readOffset)] - this.structOffset;
170 char[] className = utf8At(constValueOffset + 3, u2At(constValueOffset + 1));
171 value = new ClassSignature(className);
172 this.readOffset += 2;
173 break;
174 case '@':
175 value = new AnnotationInfo(this.reference, this.constantPoolOffsets, this.readOffset + this.structOffset, false, true);
176 this.readOffset += ((AnnotationInfo) value).readOffset;
177 break;
178 case '[':
179 int numberOfValues = u2At(this.readOffset);
180 this.readOffset += 2;
181 if (numberOfValues == 0) {
182 value = EmptyValueArray;
183 } else {
184 Object[] arrayElements = new Object[numberOfValues];
185 value = arrayElements;
186 for (int i = 0; i < numberOfValues; i++)
187 arrayElements[i] = decodeDefaultValue();
188 }
189 break;
190 default:
191 String tagDisplay = tag == 0 ? "0x00" : (char) tag + " ("+Integer.toHexString(tag&0xFF)+')'; //$NON-NLS-1$//$NON-NLS-2$
192 throw new IllegalStateException("Unrecognized tag " + tagDisplay); //$NON-NLS-1$
193 }
194 return value;
195 }
196 @Override
getElementValuePairs()197 public IBinaryElementValuePair[] getElementValuePairs() {
198 if (this.pairs == null)
199 lazyInitialize();
200 return this.pairs;
201 }
202 @Override
getTypeName()203 public char[] getTypeName() {
204 return this.typename;
205 }
206 @Override
isDeprecatedAnnotation()207 public boolean isDeprecatedAnnotation() {
208 return (this.standardAnnotationTagBits & (TagBits.AnnotationDeprecated | TagBits.AnnotationTerminallyDeprecated)) != 0;
209 }
initialize()210 void initialize() {
211 if (this.pairs == null)
212 decodeAnnotation();
213 }
lazyInitialize()214 synchronized void lazyInitialize() {
215 if (this.pairs == null)
216 decodeAnnotation();
217 }
readRetentionPolicy(int offset)218 private int readRetentionPolicy(int offset) {
219 int currentOffset = offset;
220 int tag = u1At(currentOffset);
221 currentOffset++;
222 switch (tag) {
223 case 'e':
224 int utf8Offset = this.constantPoolOffsets[u2At(currentOffset)] - this.structOffset;
225 char[] typeName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
226 currentOffset += 2;
227 if (typeName.length == 38 && CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_RETENTIONPOLICY)) {
228 utf8Offset = this.constantPoolOffsets[u2At(currentOffset)] - this.structOffset;
229 char[] constName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
230 this.standardAnnotationTagBits |= Annotation.getRetentionPolicy(constName);
231 }
232 currentOffset += 2;
233 break;
234 case 'B':
235 case 'C':
236 case 'D':
237 case 'F':
238 case 'I':
239 case 'J':
240 case 'S':
241 case 'Z':
242 case 's':
243 case 'c':
244 currentOffset += 2;
245 break;
246 case '@':
247 // none of the supported standard annotation are in the nested
248 // level.
249 currentOffset = scanAnnotation(currentOffset, false, false);
250 break;
251 case '[':
252 int numberOfValues = u2At(currentOffset);
253 currentOffset += 2;
254 for (int i = 0; i < numberOfValues; i++)
255 currentOffset = scanElementValue(currentOffset);
256 break;
257 default:
258 throw new IllegalStateException();
259 }
260 return currentOffset;
261 }
readTargetValue(int offset)262 private int readTargetValue(int offset) {
263 int currentOffset = offset;
264 int tag = u1At(currentOffset);
265 currentOffset++;
266 switch (tag) {
267 case 'e':
268 int utf8Offset = this.constantPoolOffsets[u2At(currentOffset)] - this.structOffset;
269 char[] typeName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
270 currentOffset += 2;
271 if (typeName.length == 34 && CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_ELEMENTTYPE)) {
272 utf8Offset = this.constantPoolOffsets[u2At(currentOffset)] - this.structOffset;
273 char[] constName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
274 this.standardAnnotationTagBits |= Annotation.getTargetElementType(constName);
275 }
276 currentOffset += 2;
277 break;
278 case 'B':
279 case 'C':
280 case 'D':
281 case 'F':
282 case 'I':
283 case 'J':
284 case 'S':
285 case 'Z':
286 case 's':
287 case 'c':
288 currentOffset += 2;
289 break;
290 case '@':
291 // none of the supported standard annotation are in the nested
292 // level.
293 currentOffset = scanAnnotation(currentOffset, false, false);
294 break;
295 case '[':
296 int numberOfValues = u2At(currentOffset);
297 currentOffset += 2;
298 if (numberOfValues == 0) {
299 this.standardAnnotationTagBits |= TagBits.AnnotationTarget;
300 } else {
301 for (int i = 0; i < numberOfValues; i++)
302 currentOffset = readTargetValue(currentOffset);
303 }
304 break;
305 default:
306 throw new IllegalStateException();
307 }
308 return currentOffset;
309 }
310 /**
311 * Read through this annotation in order to figure out the necessary tag
312 * bits and the length of this annotation. The data structure will not be
313 * flushed out.
314 *
315 * The tag bits are derived from the following (supported) standard
316 * annotation. java.lang.annotation.Documented,
317 * java.lang.annotation.Retention, java.lang.annotation.Target, and
318 * java.lang.Deprecated
319 *
320 * @param expectRuntimeVisibleAnno
321 * <code>true</cod> to indicate that this is a runtime-visible annotation
322 * @param toplevel <code>false</code> to indicate that an nested annotation is read.
323 * <code>true</code> otherwise
324 * @return the next offset to read.
325 */
scanAnnotation(int offset, boolean expectRuntimeVisibleAnno, boolean toplevel)326 private int scanAnnotation(int offset, boolean expectRuntimeVisibleAnno, boolean toplevel) {
327 int currentOffset = offset;
328 int utf8Offset = this.constantPoolOffsets[u2At(offset)] - this.structOffset;
329 char[] typeName = utf8At(utf8Offset + 3, u2At(utf8Offset + 1));
330 if (toplevel)
331 this.typename = typeName;
332 int numberOfPairs = u2At(offset + 2);
333 // u2 type_index + u2 number_member_value_pair
334 currentOffset += 4;
335 if (expectRuntimeVisibleAnno && toplevel) {
336 switch (typeName.length) {
337 case 22:
338 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_DEPRECATED)) {
339 this.standardAnnotationTagBits |= TagBits.AnnotationDeprecated;
340 }
341 break;
342 case 23:
343 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_SAFEVARARGS)) {
344 this.standardAnnotationTagBits |= TagBits.AnnotationSafeVarargs;
345 return currentOffset;
346 }
347 break;
348 case 29:
349 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_TARGET)) {
350 currentOffset += 2;
351 return readTargetValue(currentOffset);
352 }
353 break;
354 case 32:
355 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_RETENTION)) {
356 currentOffset += 2;
357 return readRetentionPolicy(currentOffset);
358 }
359 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_INHERITED)) {
360 this.standardAnnotationTagBits |= TagBits.AnnotationInherited;
361 return currentOffset;
362 }
363 break;
364 case 33:
365 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_ANNOTATION_DOCUMENTED)) {
366 this.standardAnnotationTagBits |= TagBits.AnnotationDocumented;
367 return currentOffset;
368 }
369 break;
370 case 52:
371 if (CharOperation.equals(typeName, ConstantPool.JAVA_LANG_INVOKE_METHODHANDLE_POLYMORPHICSIGNATURE)) {
372 this.standardAnnotationTagBits |= TagBits.AnnotationPolymorphicSignature;
373 return currentOffset;
374 }
375 break;
376 }
377 }
378 for (int i = 0; i < numberOfPairs; i++) {
379 // u2 member_name_index
380 currentOffset += 2;
381 currentOffset = scanElementValue(currentOffset);
382 }
383 return currentOffset;
384 }
385 /**
386 * @param offset
387 * the offset to start reading.
388 * @return the next offset to read.
389 */
scanElementValue(int offset)390 private int scanElementValue(int offset) {
391 int currentOffset = offset;
392 int tag = u1At(currentOffset);
393 currentOffset++;
394 switch (tag) {
395 case 'Z':
396 if ((this.standardAnnotationTagBits & TagBits.AnnotationDeprecated) != 0) {
397 // assume member_name is 'since', because @Deprecated has only one boolean member
398 int constantOffset = this.constantPoolOffsets[u2At(currentOffset)] - this.structOffset + 1;
399 if (i4At(constantOffset) == 1) {
400 this.standardAnnotationTagBits |= TagBits.AnnotationTerminallyDeprecated;
401 }
402 }
403 currentOffset += 2;
404 break;
405 case 'B':
406 case 'C':
407 case 'D':
408 case 'F':
409 case 'I':
410 case 'J':
411 case 'S':
412 case 's':
413 case 'c':
414 currentOffset += 2;
415 break;
416 case 'e':
417 currentOffset += 4;
418 break;
419 case '@':
420 // none of the supported standard annotation are in the nested
421 // level.
422 currentOffset = scanAnnotation(currentOffset, false, false);
423 break;
424 case '[':
425 int numberOfValues = u2At(currentOffset);
426 currentOffset += 2;
427 for (int i = 0; i < numberOfValues; i++)
428 currentOffset = scanElementValue(currentOffset);
429 break;
430 default:
431 throw new IllegalStateException();
432 }
433 return currentOffset;
434 }
435 @Override
toString()436 public String toString() {
437 return BinaryTypeFormatter.annotationToString(this);
438 }
439 @Override
hashCode()440 public int hashCode() {
441 final int prime = 31;
442 int result = 1;
443 result = prime * result + Util.hashCode(this.pairs);
444 result = prime * result + CharOperation.hashCode(this.typename);
445 return result;
446 }
447 @Override
equals(Object obj)448 public boolean equals(Object obj) {
449 if (this == obj) {
450 return true;
451 }
452 if (obj == null) {
453 return false;
454 }
455 if (getClass() != obj.getClass()) {
456 return false;
457 }
458 AnnotationInfo other = (AnnotationInfo) obj;
459 if (!Arrays.equals(this.pairs, other.pairs)) {
460 return false;
461 }
462 if (!Arrays.equals(this.typename, other.typename)) {
463 return false;
464 }
465 return true;
466 }
467 }
468