1 /*
2  * Copyright (C) 2009 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 signature.converter.dex;
18 
19 import java.io.IOException;
20 import java.util.Collections;
21 import java.util.EnumSet;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Set;
25 
26 import signature.converter.Visibility;
27 import signature.model.IClassDefinition;
28 import signature.model.Kind;
29 import signature.model.Modifier;
30 import signature.model.impl.SigPackage;
31 import signature.model.util.ModelUtil;
32 import dex.reader.DexBuffer;
33 import dex.reader.DexFileReader;
34 import dex.structure.DexAnnotatedElement;
35 import dex.structure.DexAnnotation;
36 import dex.structure.DexAnnotationAttribute;
37 import dex.structure.DexClass;
38 import dex.structure.DexEncodedValue;
39 import dex.structure.DexField;
40 import dex.structure.DexFile;
41 import dex.structure.DexMethod;
42 
43 
44 public class DexUtil {
45 
46     private static final String PACKAGE_INFO = "package-info";
47     private static final String THROWS_ANNOTATION =
48             "Ldalvik/annotation/Throws;";
49     private static final String SIGNATURE_ANNOTATION =
50             "Ldalvik/annotation/Signature;";
51     private static final String ANNOTATION_DEFAULT_ANNOTATION =
52             "Ldalvik/annotation/AnnotationDefault;";
53     private static final String ENCLOSING_CLASS_ANNOTATION =
54             "Ldalvik/annotation/EnclosingClass;";
55     private static final String ENCLOSING_METHOD_ANNOTATION =
56             "Ldalvik/annotation/EnclosingMethod;";
57     private static final String INNER_CLASS_ANNOTATION =
58             "Ldalvik/annotation/InnerClass;";
59     private static final String MEMBER_CLASS_ANNOTATION =
60             "Ldalvik/annotation/MemberClasses;";
61     private static final String JAVA_LANG_OBJECT = "Ljava/lang/Object;";
62 
63     private static final Set<String> INTERNAL_ANNOTATION_NAMES;
64 
65     static {
66         Set<String> tmp = new HashSet<String>();
67         tmp.add(THROWS_ANNOTATION);
68         tmp.add(SIGNATURE_ANNOTATION);
69         tmp.add(ANNOTATION_DEFAULT_ANNOTATION);
70         tmp.add(ENCLOSING_CLASS_ANNOTATION);
71         tmp.add(ENCLOSING_METHOD_ANNOTATION);
72         tmp.add(INNER_CLASS_ANNOTATION);
73         tmp.add(MEMBER_CLASS_ANNOTATION);
74         INTERNAL_ANNOTATION_NAMES = Collections.unmodifiableSet(tmp);
75     }
76 
DexUtil()77     private DexUtil() {
78         // not constructable from outside
79     }
80 
81     /**
82      * "La/b/c/A;" -> "a.b.c" "LA;" -> "" empty string
83      *
84      * @param classIdentifier
85      * @return the package name
86      */
getPackageName(String classIdentifier)87     public static String getPackageName(String classIdentifier) {
88         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
89         return ModelUtil.getPackageName(name.replace("/", "."));
90     }
91 
92     /**
93      * "La/b/c/A;" -> "A" "LA;" -> "A"
94      *
95      * @param classIdentifier
96      *            the dalvik internal identifier
97      * @return the class name
98      */
getClassName(String classIdentifier)99     public static String getClassName(String classIdentifier) {
100         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
101         return ModelUtil.getClassName(name.replace("/", ".")).replace('$', '.');
102     }
103 
getQualifiedName(String classIdentifier)104     public static String getQualifiedName(String classIdentifier) {
105         String name = removeTrailingSemicolon(removeHeadingL(classIdentifier));
106         return name.replace('/', '.');
107     }
108 
removeHeadingL(String className)109     private static String removeHeadingL(String className) {
110         assert className.startsWith("L");
111         return className.substring(1);
112     }
113 
removeTrailingSemicolon(String className)114     private static String removeTrailingSemicolon(String className) {
115         assert className.endsWith(";");
116         return className.substring(0, className.length() - 1);
117     }
118 
getDexName(String packageName, String className)119     public static String getDexName(String packageName, String className) {
120         return "L" + packageName.replace('.', '/') + "/"
121                 + className.replace('.', '$') + ";";
122     }
123 
getDexName(IClassDefinition sigClass)124     public static String getDexName(IClassDefinition sigClass) {
125         return getDexName(sigClass.getPackageName(), sigClass.getName());
126     }
127 
128     /**
129      * Returns correct modifiers for inner classes
130      */
getClassModifiers(DexClass clazz)131     public static int getClassModifiers(DexClass clazz) {
132         int modifiers = 0;
133         if (isInnerClass(clazz)) {
134             Integer accessFlags = (Integer) getAnnotationAttributeValue(
135                     getAnnotation(clazz, INNER_CLASS_ANNOTATION),
136                             "accessFlags");
137             modifiers = accessFlags.intValue();
138         } else {
139             modifiers = clazz.getModifiers();
140         }
141         return modifiers;
142     }
143 
144     /**
145      * Returns a set containing all modifiers for the given int.
146      *
147      * @param mod
148      *            the original bit coded modifiers as specified by
149      *            {@link java.lang.reflect.Modifier}
150      * @return a set containing {@link signature.model.Modifier} elements
151      */
getModifier(int mod)152     public static Set<Modifier> getModifier(int mod) {
153         Set<Modifier> modifiers = EnumSet.noneOf(Modifier.class);
154         if (java.lang.reflect.Modifier.isAbstract(mod))
155             modifiers.add(Modifier.ABSTRACT);
156         if (java.lang.reflect.Modifier.isFinal(mod))
157             modifiers.add(Modifier.FINAL);
158         // if (java.lang.reflect.Modifier.isNative(mod))
159         // modifiers.add(Modifier.NATIVE);
160         if (java.lang.reflect.Modifier.isPrivate(mod))
161             modifiers.add(Modifier.PRIVATE);
162         if (java.lang.reflect.Modifier.isProtected(mod))
163             modifiers.add(Modifier.PROTECTED);
164         if (java.lang.reflect.Modifier.isPublic(mod))
165             modifiers.add(Modifier.PUBLIC);
166         if (java.lang.reflect.Modifier.isStatic(mod))
167             modifiers.add(Modifier.STATIC);
168         // if (java.lang.reflect.Modifier.isStrict(mod))
169         // modifiers.add(Modifier.STRICT);
170         // if (java.lang.reflect.Modifier.isSynchronized(mod))
171         // modifiers.add(Modifier.SYNCHRONIZED);
172         // if (java.lang.reflect.Modifier.isTransient(mod))
173         // modifiers.add(Modifier.TRANSIENT);
174         if (java.lang.reflect.Modifier.isVolatile(mod))
175             modifiers.add(Modifier.VOLATILE);
176 
177         return modifiers;
178     }
179 
180     /**
181      * Returns true if the given class is an enumeration, false otherwise.
182      *
183      * @param dexClass
184      *            the DexClass under test
185      * @return true if the given class is an enumeration, false otherwise
186      */
isEnum(DexClass dexClass)187     public static boolean isEnum(DexClass dexClass) {
188         return (getClassModifiers(dexClass) & 0x4000) > 0;
189     }
190 
191     /**
192      * Returns true if the given class is an interface, false otherwise.
193      *
194      * @param dexClass
195      *            the DexClass under test
196      * @return true if the given class is an interface, false otherwise
197      */
isInterface(DexClass dexClass)198     public static boolean isInterface(DexClass dexClass) {
199         int modifiers = getClassModifiers(dexClass);
200         return java.lang.reflect.Modifier.isInterface(modifiers);
201     }
202 
203     /**
204      * Returns true if the given class is an annotation, false otherwise.
205      *
206      * @param dexClass
207      *            the DexClass under test
208      * @return true if the given class is an annotation, false otherwise
209      */
isAnnotation(DexClass dexClass)210     public static boolean isAnnotation(DexClass dexClass) {
211         return (getClassModifiers(dexClass) & 0x2000) > 0;
212     }
213 
isSynthetic(int modifier)214     public static boolean isSynthetic(int modifier) {
215         return (modifier & 0x1000) > 0;
216     }
217 
218     /**
219      * Returns the Kind of the given DexClass.
220      *
221      * @param dexClass
222      *            the DexClass under test
223      * @return the Kind of the given class
224      */
getKind(DexClass dexClass)225     public static Kind getKind(DexClass dexClass) {
226         // order of branches is crucial since a annotation is also an interface
227         if (isEnum(dexClass)) {
228             return Kind.ENUM;
229         } else if (isAnnotation(dexClass)) {
230             return Kind.ANNOTATION;
231         } else if (isInterface(dexClass)) {
232             return Kind.INTERFACE;
233         } else {
234             return Kind.CLASS;
235         }
236     }
237 
238     /**
239      * Returns whether the specified annotated element has an annotation with
240      * type "Ldalvik/annotation/Throws;".
241      *
242      * @param annotatedElement
243      *            the annotated element to check
244      * @return <code>true</code> if the given annotated element has the
245      *         mentioned annotation, false otherwise
246      */
declaresExceptions( DexAnnotatedElement annotatedElement)247     public static boolean declaresExceptions(
248             DexAnnotatedElement annotatedElement) {
249         return getAnnotation(annotatedElement, THROWS_ANNOTATION) != null;
250     }
251 
252     /**
253      * Returns the throws signature if the given element has such an annotation,
254      * null otherwise.
255      *
256      * @param annotatedElement
257      *            the annotated element
258      * @return he generic signature if the given element has such an annotation,
259      *         null otherwise
260      */
261     @SuppressWarnings("unchecked")
getExceptionSignature( DexAnnotatedElement annotatedElement)262     public static String getExceptionSignature(
263             DexAnnotatedElement annotatedElement) {
264         DexAnnotation annotation = getAnnotation(annotatedElement,
265                 THROWS_ANNOTATION);
266         if (annotation != null) {
267             List<DexEncodedValue> value =
268                     (List<DexEncodedValue>) getAnnotationAttributeValue(
269                             annotation, "value");
270             return concatEncodedValues(value);
271         }
272         return null;
273     }
274 
275     /**
276      * Splits a list of types:
277      * "Ljava/io/IOException;Ljava/lang/IllegalStateException;" <br>
278      * into separate type designators: <br>
279      * "Ljava/io/IOException;" , "Ljava/lang/IllegalStateException;"
280      *
281      * @param typeList
282      *            the type list
283      * @return a set of type designators
284      */
splitTypeList(String typeList)285     public static Set<String> splitTypeList(String typeList) {
286         String[] split = typeList.split(";");
287         Set<String> separateTypes = new HashSet<String>();
288         for (String string : split) {
289             separateTypes.add(string + ";");// add semicolon again
290         }
291         return separateTypes;
292     }
293 
294     /**
295      * Returns whether the specified annotated element has an annotation with
296      * type "Ldalvik/annotation/Signature;".
297      *
298      * @param annotatedElement
299      *            the annotated element to check
300      * @return <code>true</code> if the given annotated element has the
301      *         mentioned annotation, false otherwise
302      */
hasGenericSignature( DexAnnotatedElement annotatedElement)303     public static boolean hasGenericSignature(
304             DexAnnotatedElement annotatedElement) {
305         return getAnnotation(annotatedElement, SIGNATURE_ANNOTATION) != null;
306     }
307 
308     /**
309      * Returns the generic signature if the given element has such an
310      * annotation, null otherwise.
311      *
312      * @param annotatedElement
313      *            the annotated element
314      * @return he generic signature if the given element has such an annotation,
315      *         null otherwise
316      */
317     @SuppressWarnings("unchecked")
getGenericSignature( DexAnnotatedElement annotatedElement)318     public static String getGenericSignature(
319             DexAnnotatedElement annotatedElement) {
320         DexAnnotation annotation = getAnnotation(annotatedElement,
321                 SIGNATURE_ANNOTATION);
322         if (annotation != null) {
323             List<DexEncodedValue> value =
324                     (List<DexEncodedValue>) getAnnotationAttributeValue(
325                             annotation, "value");
326             return concatEncodedValues(value);
327         }
328         return null;
329     }
330 
331     /**
332      * Returns whether the specified annotated element has an annotation with
333      * type "Ldalvik/annotation/AnnotationDefault;".
334      *
335      * @param annotatedElement
336      *            the annotated element to check
337      * @return <code>true</code> if the given annotated element has the
338      *         mentioned annotation, false otherwise
339      */
hasAnnotationDefaultSignature( DexAnnotatedElement annotatedElement)340     public static boolean hasAnnotationDefaultSignature(
341             DexAnnotatedElement annotatedElement) {
342         return getAnnotation(
343                 annotatedElement, ANNOTATION_DEFAULT_ANNOTATION)!= null;
344     }
345 
346     /**
347      * Returns a mapping form annotation attribute name to its default value.
348      *
349      * @param dexClass
350      *            the class defining a annotation
351      * @return a mapping form annotation attribute name to its default value
352      */
getDefaultMappingsAnnotation( DexClass dexClass)353     public static DexAnnotation getDefaultMappingsAnnotation(
354             DexClass dexClass) {
355         return getAnnotation(dexClass, ANNOTATION_DEFAULT_ANNOTATION);
356     }
357 
358     /**
359      * Returns the annotation with the specified type from the given element or
360      * null if no such annotation is available.
361      *
362      * @param element
363      *            the annotated element
364      * @param annotationType
365      *            the dex internal name of the annotation type
366      * @return the annotation with the specified type or null if not present
367      */
getAnnotation(DexAnnotatedElement element, String annotationType)368     public static DexAnnotation getAnnotation(DexAnnotatedElement element,
369             String annotationType) {
370         assert element != null;
371         assert annotationType != null;
372 
373         for (DexAnnotation anno : element.getAnnotations()) {
374             if (annotationType.equals(anno.getTypeName())) {
375                 return anno;
376             }
377         }
378         return null;
379     }
380 
381     /**
382      * Returns the value for the specified attribute name of the given
383      * annotation or null if not present.
384      *
385      * @param annotation
386      *            the annotation
387      * @param attributeName
388      *            the name of the attribute
389      * @return the value for the specified attribute
390      */
getAnnotationAttributeValue(DexAnnotation annotation, String attributeName)391     public static Object getAnnotationAttributeValue(DexAnnotation annotation,
392             String attributeName) {
393         for (DexAnnotationAttribute dexAnnotationAttribute : annotation
394                 .getAttributes()) {
395             if (attributeName.equals(dexAnnotationAttribute.getName())) {
396                 return dexAnnotationAttribute.getEncodedValue().getValue();
397             }
398         }
399         return null;
400     }
401 
concatEncodedValues(List<DexEncodedValue> values)402     private static String concatEncodedValues(List<DexEncodedValue> values) {
403         StringBuilder builder = new StringBuilder();
404         for (DexEncodedValue string : values) {
405             builder.append(string.getValue());
406         }
407         return builder.toString();
408     }
409 
410     /**
411      * Returns true if the given method is a constructor, false otherwise.
412      *
413      * @param method
414      *            the method to test
415      * @return true if the given method is a constructor, false otherwise
416      */
isConstructor(DexMethod method)417     public static boolean isConstructor(DexMethod method) {
418         return "<init>".equals(method.getName());
419     }
420 
421     /**
422      * Returns true if the given method is a static constructor, false
423      * otherwise.
424      *
425      * @param method
426      *            the method to test
427      * @return true if the given method is a static constructor, false otherwise
428      */
isStaticConstructor(DexMethod method)429     public static boolean isStaticConstructor(DexMethod method) {
430         return "<clinit>".equals(method.getName());
431     }
432 
isMethod(DexMethod method)433     public static boolean isMethod(DexMethod method) {
434         return !isConstructor(method) && !isStaticConstructor(method);
435     }
436 
437     /**
438      * Returns the package-info class for the given package.
439      *
440      * @param aPackage
441      *            the package
442      * @return the class called "package-info" or null, if not available
443      */
findPackageInfo(SigPackage aPackage)444     public static IClassDefinition findPackageInfo(SigPackage aPackage) {
445         for (IClassDefinition clazz : aPackage.getClasses()) {
446             if (PACKAGE_INFO.equals(clazz.getName())) {
447                 return clazz;
448             }
449         }
450         return null;
451     }
452 
isPackageInfo(DexClass clazz)453     public static boolean isPackageInfo(DexClass clazz) {
454         return PACKAGE_INFO.equals(getClassName(clazz.getName()));
455     }
456 
isInternalAnnotation(DexAnnotation dexAnnotation)457     public static boolean isInternalAnnotation(DexAnnotation dexAnnotation) {
458         return INTERNAL_ANNOTATION_NAMES.contains(dexAnnotation.getTypeName());
459     }
460 
461     /**
462      * An InnerClass annotation is attached to each class which is defined in
463      * the lexical scope of another class's definition. Any class which has this
464      * annotation must also have either an EnclosingClass annotation or an
465      * EnclosingMethod annotation.
466      */
isInnerClass(DexClass clazz)467     public static boolean isInnerClass(DexClass clazz) {
468         return getAnnotation(clazz, INNER_CLASS_ANNOTATION) != null;
469     }
470 
471     /**
472      * An EnclosingClass annotation is attached to each class which is either
473      * defined as a member of another class, per se, or is anonymous but not
474      * defined within a method body (e.g., a synthetic inner class). Every class
475      * that has this annotation must also have an InnerClass annotation.
476      * Additionally, a class may not have both an EnclosingClass and an
477      * EnclosingMethod annotation.
478      */
isEnclosingClass(DexClass clazz)479     public static boolean isEnclosingClass(DexClass clazz) {
480         return getAnnotation(clazz, ENCLOSING_CLASS_ANNOTATION) != null;
481     }
482 
declaresMemberClasses(DexClass dexClass)483     public static boolean declaresMemberClasses(DexClass dexClass) {
484         return getAnnotation(dexClass, MEMBER_CLASS_ANNOTATION) != null;
485     }
486 
487     @SuppressWarnings("unchecked")
getMemberClassNames(DexClass dexClass)488     public static Set<String> getMemberClassNames(DexClass dexClass) {
489         DexAnnotation annotation = getAnnotation(dexClass,
490                 MEMBER_CLASS_ANNOTATION);
491         List<DexEncodedValue> enclosedClasses =
492                 (List<DexEncodedValue>) getAnnotationAttributeValue(
493                         annotation, "value");
494         Set<String> enclosedClassesNames = new HashSet<String>();
495         for (DexEncodedValue string : enclosedClasses) {
496             enclosedClassesNames.add((String) string.getValue());
497         }
498         return enclosedClassesNames;
499     }
500 
501 
getEnclosingClassName(DexClass dexClass)502     public static String getEnclosingClassName(DexClass dexClass) {
503         DexAnnotation annotation = getAnnotation(dexClass,
504                 ENCLOSING_CLASS_ANNOTATION);
505         String value = (String) getAnnotationAttributeValue(annotation,
506                 "value");
507         return value;
508     }
509 
convertAnyWay(DexClass dexClass)510     public static boolean convertAnyWay(DexClass dexClass) {
511         return !isSynthetic(getClassModifiers(dexClass))
512                 && !isAnonymousClassName(dexClass.getName())
513                 || isPackageInfo(dexClass);
514     }
515 
isVisible(DexClass dexClass, Visibility visibility)516     public static boolean isVisible(DexClass dexClass, Visibility visibility) {
517         // package info is always visible
518         if (isPackageInfo(dexClass)) {
519             return true;
520         }
521 
522         if (isDeclaredInMethod(dexClass)) {
523             return false;
524         }
525 
526         if (isAnonymousClassName(dexClass.getName())) {
527             return false;
528         }
529 
530         int modifiers = getClassModifiers(dexClass);
531 
532         return isVisible(modifiers, visibility);
533     }
534 
isDeclaredInMethod(DexClass dexClass)535     private static boolean isDeclaredInMethod(DexClass dexClass) {
536         return getAnnotation(dexClass, ENCLOSING_METHOD_ANNOTATION) != null;
537     }
538 
539     /**
540      * Returns whether the given dex identifier is an anonymous class name.
541      * Format: La/b/C$1;
542      *
543      * @param dexName
544      *            the name to analyze
545      * @return whether the given dex identifier is an anonymous class name
546      */
isAnonymousClassName(String dexName)547     public static boolean isAnonymousClassName(String dexName) {
548         int index = dexName.lastIndexOf('$');
549         return (index != 0) ? Character.isDigit(dexName.charAt(index + 1))
550                 : false;
551     }
552 
isVisible(DexField dexField, Visibility visibility)553     public static boolean isVisible(DexField dexField, Visibility visibility) {
554         return isVisible(dexField.getModifiers(), visibility);
555     }
556 
isVisible(DexMethod dexMethod, Visibility visibility)557     public static boolean isVisible(DexMethod dexMethod,
558             Visibility visibility) {
559         return isVisible(dexMethod.getModifiers(), visibility);
560     }
561 
isVisible(int modifiers, Visibility visibility)562     private static boolean isVisible(int modifiers, Visibility visibility) {
563 
564         if (isSynthetic(modifiers)) {
565             return false;
566         }
567 
568         Set<Modifier> elementModifiers = getModifier(modifiers);
569         if (elementModifiers.contains(Modifier.PUBLIC)) {
570             return true;
571         } else if (elementModifiers.contains(Modifier.PROTECTED)) {
572             return visibility == Visibility.PROTECTED
573                     || visibility == Visibility.PACKAGE
574                     || visibility == Visibility.PRIVATE;
575         } else if (elementModifiers.contains(Modifier.PRIVATE)) {
576             return visibility == Visibility.PRIVATE;
577         } else {
578             return visibility == Visibility.PACKAGE
579                     || visibility == Visibility.PRIVATE;
580         }
581     }
582 
getDexFiles(Set<String> fileNames)583     public static Set<DexFile> getDexFiles(Set<String> fileNames)
584             throws IOException {
585         Set<DexFile> parsedFiles = new HashSet<DexFile>();
586 
587         for (String dexFile : fileNames) {
588             DexFileReader reader = new DexFileReader();
589             DexBuffer dexBuffer = new DexBuffer(dexFile);
590             parsedFiles.add(reader.read(dexBuffer));
591         }
592         return parsedFiles;
593     }
594 
595 
isJavaLangObject(DexClass dexClass)596     public static boolean isJavaLangObject(DexClass dexClass) {
597         assert dexClass != null;
598         return JAVA_LANG_OBJECT.equals(dexClass.getName());
599     }
600 }
601