1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.dx.cf.direct;
18 
19 import com.android.dx.cf.iface.ParseException;
20 import com.android.dx.cf.iface.ParseObserver;
21 import com.android.dx.rop.annotation.Annotation;
22 import com.android.dx.rop.annotation.AnnotationVisibility;
23 import com.android.dx.rop.annotation.Annotations;
24 import com.android.dx.rop.annotation.AnnotationsList;
25 import com.android.dx.rop.annotation.NameValuePair;
26 import com.android.dx.rop.cst.Constant;
27 import com.android.dx.rop.cst.ConstantPool;
28 import com.android.dx.rop.cst.CstAnnotation;
29 import com.android.dx.rop.cst.CstArray;
30 import com.android.dx.rop.cst.CstBoolean;
31 import com.android.dx.rop.cst.CstByte;
32 import com.android.dx.rop.cst.CstChar;
33 import com.android.dx.rop.cst.CstDouble;
34 import com.android.dx.rop.cst.CstEnumRef;
35 import com.android.dx.rop.cst.CstFloat;
36 import com.android.dx.rop.cst.CstInteger;
37 import com.android.dx.rop.cst.CstLong;
38 import com.android.dx.rop.cst.CstNat;
39 import com.android.dx.rop.cst.CstShort;
40 import com.android.dx.rop.cst.CstString;
41 import com.android.dx.rop.cst.CstType;
42 import com.android.dx.rop.type.Type;
43 import com.android.dx.util.ByteArray;
44 import com.android.dx.util.Hex;
45 import java.io.IOException;
46 
47 /**
48  * Parser for annotations.
49  */
50 public final class AnnotationParser {
51     /** {@code non-null;} class file being parsed */
52     private final DirectClassFile cf;
53 
54     /** {@code non-null;} constant pool to use */
55     private final ConstantPool pool;
56 
57     /** {@code non-null;} bytes of the attribute data */
58     private final ByteArray bytes;
59 
60     /** {@code null-ok;} parse observer, if any */
61     private final ParseObserver observer;
62 
63     /** {@code non-null;} input stream to parse from */
64     private final ByteArray.MyDataInputStream input;
65 
66     /**
67      * {@code non-null;} cursor for use when informing the observer of what
68      * was parsed
69      */
70     private int parseCursor;
71 
72     /**
73      * Constructs an instance.
74      *
75      * @param cf {@code non-null;} class file to parse from
76      * @param offset {@code >= 0;} offset into the class file data to parse at
77      * @param length {@code >= 0;} number of bytes left in the attribute data
78      * @param observer {@code null-ok;} parse observer to notify, if any
79      */
AnnotationParser(DirectClassFile cf, int offset, int length, ParseObserver observer)80     public AnnotationParser(DirectClassFile cf, int offset, int length,
81             ParseObserver observer) {
82         if (cf == null) {
83             throw new NullPointerException("cf == null");
84         }
85 
86         this.cf = cf;
87         this.pool = cf.getConstantPool();
88         this.observer = observer;
89         this.bytes = cf.getBytes().slice(offset, offset + length);
90         this.input = bytes.makeDataInputStream();
91         this.parseCursor = 0;
92     }
93 
94     /**
95      * Parses an annotation value ({@code element_value}) attribute.
96      *
97      * @return {@code non-null;} the parsed constant value
98      */
parseValueAttribute()99     public Constant parseValueAttribute() {
100         Constant result;
101 
102         try {
103             result = parseValue();
104 
105             if (input.available() != 0) {
106                 throw new ParseException("extra data in attribute");
107             }
108         } catch (IOException ex) {
109             // ByteArray.MyDataInputStream should never throw.
110             throw new RuntimeException("shouldn't happen", ex);
111         }
112 
113         return result;
114     }
115 
116     /**
117      * Parses a parameter annotation attribute.
118      *
119      * @param visibility {@code non-null;} visibility of the parsed annotations
120      * @return {@code non-null;} the parsed list of lists of annotations
121      */
parseParameterAttribute( AnnotationVisibility visibility)122     public AnnotationsList parseParameterAttribute(
123             AnnotationVisibility visibility) {
124         AnnotationsList result;
125 
126         try {
127             result = parseAnnotationsList(visibility);
128 
129             if (input.available() != 0) {
130                 throw new ParseException("extra data in attribute");
131             }
132         } catch (IOException ex) {
133             // ByteArray.MyDataInputStream should never throw.
134             throw new RuntimeException("shouldn't happen", ex);
135         }
136 
137         return result;
138     }
139 
140     /**
141      * Parses an annotation attribute, per se.
142      *
143      * @param visibility {@code non-null;} visibility of the parsed annotations
144      * @return {@code non-null;} the list of annotations read from the attribute
145      * data
146      */
parseAnnotationAttribute( AnnotationVisibility visibility)147     public Annotations parseAnnotationAttribute(
148             AnnotationVisibility visibility) {
149         Annotations result;
150 
151         try {
152             result = parseAnnotations(visibility);
153 
154             if (input.available() != 0) {
155                 throw new ParseException("extra data in attribute");
156             }
157         } catch (IOException ex) {
158             // ByteArray.MyDataInputStream should never throw.
159             throw new RuntimeException("shouldn't happen", ex);
160         }
161 
162         return result;
163     }
164 
165     /**
166      * Parses a list of annotation lists.
167      *
168      * @param visibility {@code non-null;} visibility of the parsed annotations
169      * @return {@code non-null;} the list of annotation lists read from the attribute
170      * data
171      */
parseAnnotationsList( AnnotationVisibility visibility)172     private AnnotationsList parseAnnotationsList(
173             AnnotationVisibility visibility) throws IOException {
174         int count = input.readUnsignedByte();
175 
176         if (observer != null) {
177             parsed(1, "num_parameters: " + Hex.u1(count));
178         }
179 
180         AnnotationsList outerList = new AnnotationsList(count);
181 
182         for (int i = 0; i < count; i++) {
183             if (observer != null) {
184                 parsed(0, "parameter_annotations[" + i + "]:");
185                 changeIndent(1);
186             }
187 
188             Annotations annotations = parseAnnotations(visibility);
189             outerList.set(i, annotations);
190 
191             if (observer != null) {
192                 observer.changeIndent(-1);
193             }
194         }
195 
196         outerList.setImmutable();
197         return outerList;
198     }
199 
200     /**
201      * Parses an annotation list.
202      *
203      * @param visibility {@code non-null;} visibility of the parsed annotations
204      * @return {@code non-null;} the list of annotations read from the attribute
205      * data
206      */
parseAnnotations(AnnotationVisibility visibility)207     private Annotations parseAnnotations(AnnotationVisibility visibility)
208             throws IOException {
209         int count = input.readUnsignedShort();
210 
211         if (observer != null) {
212             parsed(2, "num_annotations: " + Hex.u2(count));
213         }
214 
215         Annotations annotations = new Annotations();
216 
217         for (int i = 0; i < count; i++) {
218             if (observer != null) {
219                 parsed(0, "annotations[" + i + "]:");
220                 changeIndent(1);
221             }
222 
223             Annotation annotation = parseAnnotation(visibility);
224             annotations.add(annotation);
225 
226             if (observer != null) {
227                 observer.changeIndent(-1);
228             }
229         }
230 
231         annotations.setImmutable();
232         return annotations;
233     }
234 
235     /**
236      * Parses a single annotation.
237      *
238      * @param visibility {@code non-null;} visibility of the parsed annotation
239      * @return {@code non-null;} the parsed annotation
240      */
parseAnnotation(AnnotationVisibility visibility)241     private Annotation parseAnnotation(AnnotationVisibility visibility)
242             throws IOException {
243         requireLength(4);
244 
245         int typeIndex = input.readUnsignedShort();
246         int numElements = input.readUnsignedShort();
247         CstString typeString = (CstString) pool.get(typeIndex);
248         CstType type = new CstType(Type.intern(typeString.getString()));
249 
250         if (observer != null) {
251             parsed(2, "type: " + type.toHuman());
252             parsed(2, "num_elements: " + numElements);
253         }
254 
255         Annotation annotation = new Annotation(type, visibility);
256 
257         for (int i = 0; i < numElements; i++) {
258             if (observer != null) {
259                 parsed(0, "elements[" + i + "]:");
260                 changeIndent(1);
261             }
262 
263             NameValuePair element = parseElement();
264             annotation.add(element);
265 
266             if (observer != null) {
267                 changeIndent(-1);
268             }
269         }
270 
271         annotation.setImmutable();
272         return annotation;
273     }
274 
275     /**
276      * Parses a {@link NameValuePair}.
277      *
278      * @return {@code non-null;} the parsed element
279      */
parseElement()280     private NameValuePair parseElement() throws IOException {
281         requireLength(5);
282 
283         int elementNameIndex = input.readUnsignedShort();
284         CstString elementName = (CstString) pool.get(elementNameIndex);
285 
286         if (observer != null) {
287             parsed(2, "element_name: " + elementName.toHuman());
288             parsed(0, "value: ");
289             changeIndent(1);
290         }
291 
292         Constant value = parseValue();
293 
294         if (observer != null) {
295             changeIndent(-1);
296         }
297 
298         return new NameValuePair(elementName, value);
299     }
300 
301     /**
302      * Parses an annotation value.
303      *
304      * @return {@code non-null;} the parsed value
305      */
parseValue()306     private Constant parseValue() throws IOException {
307         int tag = input.readUnsignedByte();
308 
309         if (observer != null) {
310             CstString humanTag = new CstString(Character.toString((char) tag));
311             parsed(1, "tag: " + humanTag.toQuoted());
312         }
313 
314         switch (tag) {
315             case 'B': {
316                 CstInteger value = (CstInteger) parseConstant();
317                 return CstByte.make(value.getValue());
318             }
319             case 'C': {
320                 CstInteger value = (CstInteger) parseConstant();
321                 int intValue = value.getValue();
322                 return CstChar.make(value.getValue());
323             }
324             case 'D': {
325                 CstDouble value = (CstDouble) parseConstant();
326                 return value;
327             }
328             case 'F': {
329                 CstFloat value = (CstFloat) parseConstant();
330                 return value;
331             }
332             case 'I': {
333                 CstInteger value = (CstInteger) parseConstant();
334                 return value;
335             }
336             case 'J': {
337                 CstLong value = (CstLong) parseConstant();
338                 return value;
339             }
340             case 'S': {
341                 CstInteger value = (CstInteger) parseConstant();
342                 return CstShort.make(value.getValue());
343             }
344             case 'Z': {
345                 CstInteger value = (CstInteger) parseConstant();
346                 return CstBoolean.make(value.getValue());
347             }
348             case 'c': {
349                 int classInfoIndex = input.readUnsignedShort();
350                 CstString value = (CstString) pool.get(classInfoIndex);
351                 Type type = Type.internReturnType(value.getString());
352 
353                 if (observer != null) {
354                     parsed(2, "class_info: " + type.toHuman());
355                 }
356 
357                 return new CstType(type);
358             }
359             case 's': {
360                 return parseConstant();
361             }
362             case 'e': {
363                 requireLength(4);
364 
365                 int typeNameIndex = input.readUnsignedShort();
366                 int constNameIndex = input.readUnsignedShort();
367                 CstString typeName = (CstString) pool.get(typeNameIndex);
368                 CstString constName = (CstString) pool.get(constNameIndex);
369 
370                 if (observer != null) {
371                     parsed(2, "type_name: " + typeName.toHuman());
372                     parsed(2, "const_name: " + constName.toHuman());
373                 }
374 
375                 return new CstEnumRef(new CstNat(constName, typeName));
376             }
377             case '@': {
378                 Annotation annotation =
379                     parseAnnotation(AnnotationVisibility.EMBEDDED);
380                 return new CstAnnotation(annotation);
381             }
382             case '[': {
383                 requireLength(2);
384 
385                 int numValues = input.readUnsignedShort();
386                 CstArray.List list = new CstArray.List(numValues);
387 
388                 if (observer != null) {
389                     parsed(2, "num_values: " + numValues);
390                     changeIndent(1);
391                 }
392 
393                 for (int i = 0; i < numValues; i++) {
394                     if (observer != null) {
395                         changeIndent(-1);
396                         parsed(0, "element_value[" + i + "]:");
397                         changeIndent(1);
398                     }
399                     list.set(i, parseValue());
400                 }
401 
402                 if (observer != null) {
403                     changeIndent(-1);
404                 }
405 
406                 list.setImmutable();
407                 return new CstArray(list);
408             }
409             default: {
410                 throw new ParseException("unknown annotation tag: " +
411                         Hex.u1(tag));
412             }
413         }
414     }
415 
416     /**
417      * Helper for {@link #parseValue}, which parses a constant reference
418      * and returns the referred-to constant value.
419      *
420      * @return {@code non-null;} the parsed value
421      */
parseConstant()422     private Constant parseConstant() throws IOException {
423         int constValueIndex = input.readUnsignedShort();
424         Constant value = (Constant) pool.get(constValueIndex);
425 
426         if (observer != null) {
427             String human = (value instanceof CstString)
428                 ? ((CstString) value).toQuoted()
429                 : value.toHuman();
430             parsed(2, "constant_value: " + human);
431         }
432 
433         return value;
434     }
435 
436     /**
437      * Helper which will throw an exception if the given number of bytes
438      * is not available to be read.
439      *
440      * @param requiredLength the number of required bytes
441      */
requireLength(int requiredLength)442     private void requireLength(int requiredLength) throws IOException {
443         if (input.available() < requiredLength) {
444             throw new ParseException("truncated annotation attribute");
445         }
446     }
447 
448     /**
449      * Helper which indicates that some bytes were just parsed. This should
450      * only be used (for efficiency sake) if the parse is known to be
451      * observed.
452      *
453      * @param length {@code >= 0;} number of bytes parsed
454      * @param message {@code non-null;} associated message
455      */
parsed(int length, String message)456     private void parsed(int length, String message) {
457         observer.parsed(bytes, parseCursor, length, message);
458         parseCursor += length;
459     }
460 
461     /**
462      * Convenience wrapper that simply calls through to
463      * {@code observer.changeIndent()}.
464      *
465      * @param indent the amount to change the indent by
466      */
changeIndent(int indent)467     private void changeIndent(int indent) {
468         observer.changeIndent(indent);
469     }
470 }
471