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