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.dex.cf;
18 
19 import com.android.dx.cf.attrib.AttAnnotationDefault;
20 import com.android.dx.cf.attrib.AttEnclosingMethod;
21 import com.android.dx.cf.attrib.AttExceptions;
22 import com.android.dx.cf.attrib.AttInnerClasses;
23 import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
24 import com.android.dx.cf.attrib.AttRuntimeInvisibleParameterAnnotations;
25 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
26 import com.android.dx.cf.attrib.AttRuntimeVisibleParameterAnnotations;
27 import com.android.dx.cf.attrib.AttSignature;
28 import com.android.dx.cf.attrib.AttSourceDebugExtension;
29 import com.android.dx.cf.attrib.InnerClassList;
30 import com.android.dx.cf.direct.DirectClassFile;
31 import com.android.dx.cf.iface.AttributeList;
32 import com.android.dx.cf.iface.Method;
33 import com.android.dx.cf.iface.MethodList;
34 import com.android.dx.dex.file.AnnotationUtils;
35 import com.android.dx.rop.annotation.Annotation;
36 import com.android.dx.rop.annotation.AnnotationVisibility;
37 import com.android.dx.rop.annotation.Annotations;
38 import com.android.dx.rop.annotation.AnnotationsList;
39 import com.android.dx.rop.annotation.NameValuePair;
40 import com.android.dx.rop.code.AccessFlags;
41 import com.android.dx.rop.cst.CstMethodRef;
42 import com.android.dx.rop.cst.CstNat;
43 import com.android.dx.rop.cst.CstType;
44 import com.android.dx.rop.type.StdTypeList;
45 import com.android.dx.rop.type.Type;
46 import com.android.dx.rop.type.TypeList;
47 import com.android.dx.util.Warning;
48 import java.util.ArrayList;
49 
50 /**
51  * Utility methods that translate various classfile attributes
52  * into forms suitable for use in creating {@code dex} files.
53  */
54 /*package*/ class AttributeTranslator {
55     /**
56      * This class is uninstantiable.
57      */
AttributeTranslator()58     private AttributeTranslator() {
59         // This space intentionally left blank.
60     }
61 
62     /**
63      * Gets the list of thrown exceptions for a given method.
64      *
65      * @param method {@code non-null;} the method in question
66      * @return {@code non-null;} the list of thrown exceptions
67      */
getExceptions(Method method)68     public static TypeList getExceptions(Method method) {
69         AttributeList attribs = method.getAttributes();
70         AttExceptions exceptions = (AttExceptions)
71             attribs.findFirst(AttExceptions.ATTRIBUTE_NAME);
72 
73         if (exceptions == null) {
74             return StdTypeList.EMPTY;
75         }
76 
77         return exceptions.getExceptions();
78     }
79 
80     /**
81      * Gets the annotations out of a given {@link AttributeList}. This
82      * combines both visible and invisible annotations into a single
83      * result set and also adds in a system annotation for the
84      * {@code Signature} attribute if present.
85      *
86      * @param attribs {@code non-null;} the attributes list to search in
87      * @return {@code non-null;} the set of annotations, which may be empty
88      */
getAnnotations(AttributeList attribs)89     public static Annotations getAnnotations(AttributeList attribs) {
90         Annotations result = getAnnotations0(attribs);
91         Annotation signature = getSignature(attribs);
92         Annotation sourceDebugExtension = getSourceDebugExtension(attribs);
93 
94         if (signature != null) {
95             result = Annotations.combine(result, signature);
96         }
97 
98         if (sourceDebugExtension != null) {
99             result = Annotations.combine(result, sourceDebugExtension);
100         }
101 
102         return result;
103     }
104 
105     /**
106      * Gets the annotations out of a given class, similar to {@link
107      * #getAnnotations}, also including annotations for translations
108      * of class-level attributes {@code EnclosingMethod} and
109      * {@code InnerClasses}, if present. Additionally, if the
110      * class is an annotation class, then this also includes a
111      * representation of all the {@code AnnotationDefault}
112      * values.
113      *
114      * @param cf {@code non-null;} the class in question
115      * @param args {@code non-null;} the high-level options
116      * @return {@code non-null;} the set of annotations, which may be empty
117      */
getClassAnnotations(DirectClassFile cf, CfOptions args)118     public static Annotations getClassAnnotations(DirectClassFile cf,
119             CfOptions args) {
120         CstType thisClass = cf.getThisClass();
121         AttributeList attribs = cf.getAttributes();
122         Annotations result = getAnnotations(attribs);
123         Annotation enclosingMethod = translateEnclosingMethod(attribs);
124 
125         try {
126             Annotations innerClassAnnotations =
127                 translateInnerClasses(thisClass, attribs,
128                         enclosingMethod == null);
129             if (innerClassAnnotations != null) {
130                 result = Annotations.combine(result, innerClassAnnotations);
131             }
132         } catch (Warning warn) {
133             args.warn.println("warning: " + warn.getMessage());
134         }
135 
136         if (enclosingMethod != null) {
137             result = Annotations.combine(result, enclosingMethod);
138         }
139 
140         if (AccessFlags.isAnnotation(cf.getAccessFlags())) {
141             Annotation annotationDefault =
142                 translateAnnotationDefaults(cf);
143             if (annotationDefault != null) {
144                 result = Annotations.combine(result, annotationDefault);
145             }
146         }
147 
148         return result;
149     }
150 
151     /**
152      * Gets the annotations out of a given method, similar to {@link
153      * #getAnnotations}, also including an annotation for the translation
154      * of the method-specific attribute {@code Exceptions}.
155      *
156      * @param method {@code non-null;} the method in question
157      * @return {@code non-null;} the set of annotations, which may be empty
158      */
getMethodAnnotations(Method method)159     public static Annotations getMethodAnnotations(Method method) {
160         Annotations result = getAnnotations(method.getAttributes());
161         TypeList exceptions = getExceptions(method);
162 
163         if (exceptions.size() != 0) {
164             Annotation throwsAnnotation =
165                 AnnotationUtils.makeThrows(exceptions);
166             result = Annotations.combine(result, throwsAnnotation);
167         }
168 
169         return result;
170     }
171 
172     /**
173      * Helper method for {@link #getAnnotations} which just gets the
174      * existing annotations, per se.
175      *
176      * @param attribs {@code non-null;} the attributes list to search in
177      * @return {@code non-null;} the set of annotations, which may be empty
178      */
getAnnotations0(AttributeList attribs)179     private static Annotations getAnnotations0(AttributeList attribs) {
180         AttRuntimeVisibleAnnotations visible =
181             (AttRuntimeVisibleAnnotations)
182             attribs.findFirst(AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
183         AttRuntimeInvisibleAnnotations invisible =
184             (AttRuntimeInvisibleAnnotations)
185             attribs.findFirst(AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
186 
187         if (visible == null) {
188             if (invisible == null) {
189                 return Annotations.EMPTY;
190             }
191             return invisible.getAnnotations();
192         }
193 
194         if (invisible == null) {
195             return visible.getAnnotations();
196         }
197 
198         // Both are non-null, so combine them.
199 
200         return Annotations.combine(visible.getAnnotations(),
201                 invisible.getAnnotations());
202     }
203 
204     /**
205      * Gets the {@code Signature} attribute out of a given
206      * {@link AttributeList}, if any, translating it to an annotation.
207      *
208      * @param attribs {@code non-null;} the attributes list to search in
209      * @return {@code null-ok;} the converted {@code Signature} annotation,
210      * if there was an attribute to translate
211      */
getSignature(AttributeList attribs)212     private static Annotation getSignature(AttributeList attribs) {
213         AttSignature signature = (AttSignature)
214             attribs.findFirst(AttSignature.ATTRIBUTE_NAME);
215 
216         if (signature == null) {
217             return null;
218         }
219 
220         return AnnotationUtils.makeSignature(signature.getSignature());
221     }
222 
223 
getSourceDebugExtension(AttributeList attribs)224     private static Annotation getSourceDebugExtension(AttributeList attribs) {
225         AttSourceDebugExtension extension = (AttSourceDebugExtension)
226             attribs.findFirst(AttSourceDebugExtension.ATTRIBUTE_NAME);
227 
228         if (extension == null) {
229             return null;
230         }
231 
232         return AnnotationUtils.makeSourceDebugExtension(extension.getSmapString());
233     }
234 
235     /**
236      * Gets the {@code EnclosingMethod} attribute out of a given
237      * {@link AttributeList}, if any, translating it to an annotation.
238      * If the class really has an enclosing method, this returns an
239      * {@code EnclosingMethod} annotation; if not, this returns
240      * an {@code EnclosingClass} annotation.
241      *
242      * @param attribs {@code non-null;} the attributes list to search in
243      * @return {@code null-ok;} the converted {@code EnclosingMethod} or
244      * {@code EnclosingClass} annotation, if there was an
245      * attribute to translate
246      */
translateEnclosingMethod(AttributeList attribs)247     private static Annotation translateEnclosingMethod(AttributeList attribs) {
248         AttEnclosingMethod enclosingMethod = (AttEnclosingMethod)
249             attribs.findFirst(AttEnclosingMethod.ATTRIBUTE_NAME);
250 
251         if (enclosingMethod == null) {
252             return null;
253         }
254 
255         CstType enclosingClass = enclosingMethod.getEnclosingClass();
256         CstNat nat = enclosingMethod.getMethod();
257 
258         if (nat == null) {
259             /*
260              * Dalvik doesn't use EnclosingMethod annotations unless
261              * there really is an enclosing method. Anonymous classes
262              * are unambiguously identified by having an InnerClass
263              * annotation with an empty name along with an appropriate
264              * EnclosingClass.
265              */
266             return AnnotationUtils.makeEnclosingClass(enclosingClass);
267         }
268 
269         return AnnotationUtils.makeEnclosingMethod(
270                 new CstMethodRef(enclosingClass, nat));
271     }
272 
273     /**
274      * Gets the {@code InnerClasses} attribute out of a given
275      * {@link AttributeList}, if any, translating it to one or more of an
276      * {@code InnerClass}, {@code EnclosingClass}, or
277      * {@code MemberClasses} annotation.
278      *
279      * @param thisClass {@code non-null;} type representing the class being
280      * processed
281      * @param attribs {@code non-null;} the attributes list to search in
282      * @param needEnclosingClass whether to include an
283      * {@code EnclosingClass} annotation
284      * @return {@code null-ok;} the converted list of annotations, if there
285      * was an attribute to translate
286      */
translateInnerClasses(CstType thisClass, AttributeList attribs, boolean needEnclosingClass)287     private static Annotations translateInnerClasses(CstType thisClass,
288             AttributeList attribs, boolean needEnclosingClass) {
289         AttInnerClasses innerClasses = (AttInnerClasses)
290             attribs.findFirst(AttInnerClasses.ATTRIBUTE_NAME);
291 
292         if (innerClasses == null) {
293             return null;
294         }
295 
296         /*
297          * Search the list for the element representing the current class
298          * as well as for any named member classes.
299          */
300 
301         InnerClassList list = innerClasses.getInnerClasses();
302         int size = list.size();
303         InnerClassList.Item foundThisClass = null;
304         ArrayList<Type> membersList = new ArrayList<Type>();
305 
306         for (int i = 0; i < size; i++) {
307             InnerClassList.Item item = list.get(i);
308             CstType innerClass = item.getInnerClass();
309             if (innerClass.equals(thisClass)) {
310                 foundThisClass = item;
311             } else if (thisClass.equals(item.getOuterClass())) {
312                 membersList.add(innerClass.getClassType());
313             }
314         }
315 
316         int membersSize = membersList.size();
317 
318         if ((foundThisClass == null) && (membersSize == 0)) {
319             return null;
320         }
321 
322         Annotations result = new Annotations();
323 
324         if (foundThisClass != null) {
325             result.add(AnnotationUtils.makeInnerClass(
326                                foundThisClass.getInnerName(),
327                                foundThisClass.getAccessFlags()));
328             if (needEnclosingClass) {
329                 CstType outer = foundThisClass.getOuterClass();
330                 if (outer == null) {
331                     throw new Warning(
332                             "Ignoring InnerClasses attribute for an " +
333                             "anonymous inner class\n" +
334                             "(" + thisClass.toHuman() +
335                             ") that doesn't come with an\n" +
336                             "associated EnclosingMethod attribute. " +
337                             "This class was probably produced by a\n" +
338                             "compiler that did not target the modern " +
339                             ".class file format. The recommended\n" +
340                             "solution is to recompile the class from " +
341                             "source, using an up-to-date compiler\n" +
342                             "and without specifying any \"-target\" type " +
343                             "options. The consequence of ignoring\n" +
344                             "this warning is that reflective operations " +
345                             "on this class will incorrectly\n" +
346                             "indicate that it is *not* an inner class.");
347                 }
348                 result.add(AnnotationUtils.makeEnclosingClass(
349                                    foundThisClass.getOuterClass()));
350             }
351         }
352 
353         if (membersSize != 0) {
354             StdTypeList typeList = new StdTypeList(membersSize);
355             for (int i = 0; i < membersSize; i++) {
356                 typeList.set(i, membersList.get(i));
357             }
358             typeList.setImmutable();
359             result.add(AnnotationUtils.makeMemberClasses(typeList));
360         }
361 
362         result.setImmutable();
363         return result;
364     }
365 
366     /**
367      * Gets the parameter annotations out of a given method. This
368      * combines both visible and invisible annotations into a single
369      * result set.
370      *
371      * @param method {@code non-null;} the method in question
372      * @return {@code non-null;} the list of annotation sets, which may be
373      * empty
374      */
getParameterAnnotations(Method method)375     public static AnnotationsList getParameterAnnotations(Method method) {
376         AttributeList attribs = method.getAttributes();
377         AttRuntimeVisibleParameterAnnotations visible =
378             (AttRuntimeVisibleParameterAnnotations)
379             attribs.findFirst(
380                     AttRuntimeVisibleParameterAnnotations.ATTRIBUTE_NAME);
381         AttRuntimeInvisibleParameterAnnotations invisible =
382             (AttRuntimeInvisibleParameterAnnotations)
383             attribs.findFirst(
384                     AttRuntimeInvisibleParameterAnnotations.ATTRIBUTE_NAME);
385 
386         if (visible == null) {
387             if (invisible == null) {
388                 return AnnotationsList.EMPTY;
389             }
390             return invisible.getParameterAnnotations();
391         }
392 
393         if (invisible == null) {
394             return visible.getParameterAnnotations();
395         }
396 
397         // Both are non-null, so combine them.
398 
399         return AnnotationsList.combine(visible.getParameterAnnotations(),
400                 invisible.getParameterAnnotations());
401     }
402 
403     /**
404      * Gets the {@code AnnotationDefault} attributes out of a
405      * given class, if any, reforming them as an
406      * {@code AnnotationDefault} annotation.
407      *
408      * @param cf {@code non-null;} the class in question
409      * @return {@code null-ok;} an appropriately-constructed
410      * {@code AnnotationDefault} annotation, if there were any
411      * annotation defaults in the class, or {@code null} if not
412      */
translateAnnotationDefaults(DirectClassFile cf)413     private static Annotation translateAnnotationDefaults(DirectClassFile cf) {
414         CstType thisClass = cf.getThisClass();
415         MethodList methods = cf.getMethods();
416         int sz = methods.size();
417         Annotation result =
418             new Annotation(thisClass, AnnotationVisibility.EMBEDDED);
419         boolean any = false;
420 
421         for (int i = 0; i < sz; i++) {
422             Method one = methods.get(i);
423             AttributeList attribs = one.getAttributes();
424             AttAnnotationDefault oneDefault = (AttAnnotationDefault)
425                 attribs.findFirst(AttAnnotationDefault.ATTRIBUTE_NAME);
426 
427             if (oneDefault != null) {
428                 NameValuePair pair = new NameValuePair(
429                         one.getNat().getName(),
430                         oneDefault.getValue());
431                 result.add(pair);
432                 any = true;
433             }
434         }
435 
436         if (! any) {
437             return null;
438         }
439 
440         result.setImmutable();
441         return AnnotationUtils.makeAnnotationDefault(result);
442     }
443 }
444